手把手带你使用webpack4构建一个Vue开发编译环境,并实现代码分割,css代码分离
本篇文章不会细致讲webpack生产编译方面的优化操作,主要点是为了区分开发与生产环境的区别,代码分割分离的操作,所以不建议各位使用本篇文章内配置内容去进行生产编译,生产编译其它优化细节请各位自行另加配置,当然本篇文章配置也不是不能用作生产配置,只是给各位一个建议~
正文
所需环境
开始之前,请各位给自己电脑安装一下Nodejs,具体安装方法这里我就不做讲解了,各位可以移步Node官网查看文档然后对应系统版本进行安装,以下是我的Node or Npm版本
{
"engines": {
"node": "10.16.0",
"npm": "6.9.0"
}
}
目录结构
.
├── dist
├── node_modules
├── public
│ ├── index.html
│ ├── images
├── build
│ ├── webpack.base.config.js
│ ├── webpack.dev.config.js
│ └── webpack.prod.config.js
├── package.json
├── package-lock.json
├── .babelrc
├── postcss.config.js
└── src
├── assets
│ ├── js
│ ├── style
│ ├── images
├── pages
│ ├── app.vue
│ ├── home
│ │ ├── index.vue
│ ├── router
│ │ ├── index.js
└── main.js
这里说一下几个重要目录,dist目录是webpack打包编译后输出目录,public目录是全局资源,build目录是webpack配置,src目录是我们开发的业务代码存放目录,请各位按以上结构创建好各目录。
首先,如果你的目录还没有package.json文件,请通过以下方式创建一个文件,请打开你电脑的命令行工具,进入到对应项目目录执行以下命令
npm init -y
执行完成后,你的项目目录内就会生成出一个package.json的配置文件
package.json中的相关依赖
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"scripts": {
"dev": "NODE_ENV=development webpack-dev-server --config ./build/webpack.dev.config.js --watch",
"build": "NODE_ENV=production webpack --config ./build/webpack.prod.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"engines": {
"node": "10.16.0",
"npm": "6.9.0"
},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5",
"autoprefixer": "^9.6.1",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^3.1.4",
"css-loader": "^3.1.0",
"file-loader": "^4.1.0",
"html-webpack-plugin": "^3.2.0",
"image-webpack-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.12.0",
"postcss-loader": "^3.0.0",
"purify-css": "^1.2.5",
"purifycss-webpack": "^0.7.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"url-loader": "^2.1.0",
"vue-loader": "^15.7.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.38.0",
"webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.7.2",
"webpack-manifest-plugin": "^2.0.4",
"webpack-merge": "^4.2.1"
},
"dependencies": {
"vue": "^2.6.10",
"vue-router": "^3.0.7",
"vuex": "^3.1.1"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie >= 9",
"ios >= 6",
"android >= 4.0"
]
}
可以看到以上配置有很多东西,这里我们主要只关注三个地方,scripts,dependencies,devDependencies。
首先在scripts中设置了dev和build,开发和生产两种模式,在dev的命令中我们指定了一个文件./build/webpack.dev.config.js,这个文件是开发配置文件;然后build中指定了./build/webpack.prod.config.js,这个是生产配置文件,这两个文件里面都是我们这个项目的开发和打包的配置内容,也是今天的主要内容,后面我会详细给大家讲解这两个文件里面的配置。
dependencies是我们生产依赖,devDependencies是开发依赖。
然后可以把这些依赖复制到你的package.json配置文件中,然后执行以下命令拉取这些所需依赖
npm install
webpack配置
根据上方目录结构可以很清晰的看到项目的webpack配置相关的内容是存放在build目录下的,所以下面我来给大家讲以下这三个文件的作用:
-
webpack.base.config.js 开发配置 or 生产配置的共用配置 -
webpack.dev.config.js 开发配置 -
webpack.prod.config.js 生产配置
webpack.base.config.js 配置
接下来,我们先看看共用配置,以下是具体配置信息
const path = require('path'),
webpack = require('webpack'),
VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
entry: './src/main.js',
resolve: {
alias: {
vue: 'vue/dist/vue.js',
"@": path.resolve(__dirname, '../src')
},
extensions: ['.ts', '.js', '.scss', '.css', '.png', '.jpg', '.jpeg', '.gif', '.vue', '.json']
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: path.resolve(__dirname, '../node_modules'),
include: path.resolve(__dirname, '../')
},
{
test: /\.(png|jpe?g|gif|bmp|svg)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192, // 小于8k将图片转换成base64
name: '[path][name].[ext]?[hash:8]'
}
},
{
loader: 'image-webpack-loader', // 图片压缩
options: {
bypassOnDebug: true
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[path][name].[ext]?[hash:8]' //path/to/file.png?e43b20c0
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
loader: 'url-loader',
options: {
limit: 8192,
name: 'fonts/[name].[hash:8].[ext]'
}
}
]
},
plugins: [
new VueLoaderPlugin(),
//编译进度
new webpack.ProgressPlugin(),
]
}
根据以上配置信息,首先配置了项目编译的入口,指定了入口src目录下面的main.js文件,主要通过这个入口去编译我们业务代码的各个代码块
resolve: {
alias: {
vue: 'vue/dist/vue.js',
"@": path.resolve(__dirname, '../src')
},
extensions: ['.ts', '.js', '.scss', '.css', '.png', '.jpg', '.jpeg', '.gif', '.vue', '.json']
}
alias 配置了目录的别名,方便后面引用模块时可直接通过别名去查找文件。
extensions 配置是为了在后面业务开发中通过import或者require去引入模块时,不需要去填入文件的后缀。
module 主要配置代码的编译与文件的各种loader处理,根据配置我们可以看到,主要分别处理了.vue文件的编译,.js文件的编译,对图片,字体,音乐文件的处理。
plugins 配置插件,首先我们加入了针对处理vue的loader插件,然后再加了一个显示编译进度的插件。
webpack.dev.config.js配置
接下来,我在下方列出开发环境相关配置信息
const merge = require('webpack-merge'),
webpack = require('webpack'),
webpackBaseConfig = require('./webpack.base.config'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
path = require('path')
module.exports = merge(webpackBaseConfig, {
mode: 'development',
devtool: 'inline-source-map',
output: {
filename: `[name].[hash:8].js`,
chunkFilename: `[name].[hash:8].js`,
path: path.resolve(__dirname, '../dist')
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true,
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
// you can also read from a file, e.g. `variables.scss`
data: `$color: red;`
}
}
]
}
]
},
plugins: [
//热更新
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
title: 'webpack-demo',
filename: path.resolve(__dirname, '../dist/index.html'),
template: path.resolve(__dirname, '../public/index.html'),
favicon: ''
}),
//持久化缓存
new webpack.NamedModulesPlugin()
],
devServer: {
contentBase: path.resolve(__dirname, '../dist'),
open: true,
host: 'localhost',
port: 8080,
hot: true,
compress: true,//服务器压缩
proxy: {},
progress: true
}
})
以上配置通过webpack-merge把webpack.base.config.js的配置合并进来了,然后补充了一些开发环境相关配置。
mode配置设置了当前打包的方式,为开发模式。
devtool配置主要作用是为了业务开发中,如果发生了逻辑错误,此配置会告诉开发者报错代码的具体位置,当然它的取值也有多个,所以具体请移步webpack的官方文档进行查看。
output输出编译后文件相关配置,里面的chunkFilename的作用稍后讲解生产配置时再做说明。
module配置中主要针对开发环境对css与scss编译处理,主要使用了vue-style-loader,css-loader,postcss-loader,sass-loader。
plugins主要配置热更新,html的处理以及缓存处理。
devServer是webpack-dev-server的相关配置,主要是启动一个开发服务,方便开发时能够实时看到编写内容
webpack.prod.config.js配置
const merge = require('webpack-merge'),
webpackBaseConfig = require('./webpack.base.config'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
path = require('path'),
glob = require('glob'),
webpack = require('webpack'),
{ CleanWebpackPlugin } = require('clean-webpack-plugin'),
ManifestPlugin = require('webpack-manifest-plugin'),
MiniCssExtractPlugin = require('mini-css-extract-plugin'),
PurifyCSSPlugin = require('purifycss-webpack')
// UglifyJsPlugin = require('uglifyjs-webpack-plugin'),
// OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'),
module.exports = merge(webpackBaseConfig, {
mode: 'production',
devtool: 'none',
output: {
filename: `[name].[chunkhash:8].js`,
chunkFilename: `[name].[chunkhash:8].js`,
path: path.resolve(__dirname, '../dist'),
publicPath: "/"
},
optimization: {
// minimizer: [
// new UglifyJsPlugin({
// cache: true,
// parallel: true,
// sourcMap: true
// }),
// new OptimizeCSSAssetsPlugin({}),
// ],
splitChunks: {
chunks: "all",
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/, // 匹配node_modules目录下的文件
priority: -10 // 优先级配置项
},
default: {
minChunks: 2,
priority: -20, // 优先级配置项
reuseExistingChunk: true
}
}
}
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {}
},
{
loader: 'postcss-loader',
options: {}
},
{
loader: 'sass-loader',
options: {
indentedSyntax: true,
// you can also read from a file, e.g. `variables.scss`
data: `$color: red;`
}
},
]
},
]
},
plugins: [
//持久化缓存
new webpack.HashedModuleIdsPlugin(),
//清目录
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'webpack-demo',
filename: path.resolve(__dirname, '../dist/index.html'),
template: path.resolve(__dirname, '../public/index.html'),
minify: {
removeRedundantAttributes: true, // 删除多余的属性
collapseWhitespace: true, // 折叠空白区域
removeAttributeQuotes: true, // 移除属性的引号
removeComments: true, // 移除注释
collapseBooleanAttributes: true // 省略只有 boolean 值的属性值 例如:readonly checked
},
favicon: ''
}),
//提取css
new MiniCssExtractPlugin({
filename: 'assets/style/[name].[chunkhash:8].css',
chunkFileName: 'assets/style/[name].[chunkhash:8].css',
allChunks: true
}),
//删除多余的CSS
new PurifyCSSPlugin({
paths: glob.sync(path.join(__dirname, '../public/*.html'))
}),
//生成manifest清单
new ManifestPlugin()
]
})
首先,它和开发配置一样,把webpack.base.config.js合并进来了,然后扩展了新的配置,设置了mode为生产环境,devtool给关闭了,你也可以开启devtool但是这会影响打包速度,下面主要说一下几个配置:
optimization配置是webpack4才加的,它的作用是用来分割公共代码的,当webpack的mode设为生产模式时,optimization的配置会默认开启。
optimization的参数说明
chunks:表示从哪些chunks里面抽取代码,除了三个可选字符串值 initial、async、all 之外,还可以通过函数来过滤所需的 chunks;
minSize:表示抽取出来的文件在压缩前的最小大小,默认为 30000;
maxSize:表示抽取出来的文件在压缩前的最大大小,默认为 0,表示不限制最大大小;
minChunks:表示被引用次数,默认为1;上述配置commons中minChunks为2,表示将被多次引用的代码抽离成commons。
值得注意的是,如果没有修改minSize属性的话,而且公用的代码size小于30KB的话,它就不会分割成一个单独的文件。在真实情形下,这是合理的,因为(如分割)并不能带来性能的提升,反而使得浏览器多了一次对资源的请求。
maxAsyncRequests:最大的按需(异步)加载次数,默认为 5;
maxInitialRequests:最大的初始化加载次数,默认为 3;
automaticNameDelimiter:抽取出来的文件的自动生成名字的分割符,默认为 ~;
name:抽取出来文件的名字,默认为 true,表示自动生成文件名;
cacheGroups: 缓存组。(这才是配置的关键)
缓存组会继承splitChunks的配置,但是 test、priorty和reuseExistingChunk只能用于配置缓存组 。cacheGroups是一个对象,按上述介绍的键值对方式来配置即可,值代表对应的选项。除此之外,所有上面列出的选择都是可以用在缓存组里的:chunks, minSize, minChunks, maxAsyncRequests, maxInitialRequests, name。可以通过optimization.splitChunks.cacheGroups.default: false禁用default缓存组。默认缓存组的优先级(priotity)是负数,因此所有自定义缓存组都可以有比它更高优先级(译注:更高优先级的缓存组可以优先打包所选择的模块)(默认自定义缓存组优先级为0)
chunkFilename
个人理解chunkFilename就是未被列在entry中,但有些场景需要被打包出来的文件命名配置。比如按需加载(异步)模块的时候,这样的文件是没有被列在entry中的使用CommonJS的方式异步加载模块。比如:
require.ensure(["modules/index.js"], function(require) {
var a = require("modules/index.js");
// ...
}, 'app');
异步加载的模块是要以文件形式加载,所以这时生成的文件名是以chunkname配置的,生成出的文件名就是app.min.js。
require.ensure 第三个参数是给这个模块命名,否则 chunkFilename: "[name].min.js"中的 [name]是一个自动分配的、可读性很差的id。
当然,异步加载模块的写法还有一种方式,就是通过es6的import。比如:
import(
/* webpackChunkName: 'pages/home/index' */
'@/pages/home')
以上代码执行后就会输出一个pages目录然后home目录里面有一个index.acw9gpl0m.js文件,当然chunkhash我是随便输入的,这个以打包的chunkhash为准。
如果使用这种方式需要添加一个.babelrc文件,具体配置
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-transform-runtime",
"@babel/plugin-syntax-dynamic-import"
]
}
MiniCssExtractPlugin
miniCssExtractPlugin将CSS提取为独立的文件的插件,对每个包含css的js文件都会创建一个CSS文件,支持按需加载css和sourceMap
只能用在webpack4中,对比另一个插件 extract-text-webpack-plugin优点:
-
异步加载 -
不重复编译,性能更好 -
更容易使用 -
只针对CSS
这里目前配置是没有配置压缩的,如果需要生产压缩,可以使用optimize-css-assets-webpack-plugin 插件。设置 optimization.minimizer 覆盖webpack默认提供的,确保也指定一个JS压缩器,具体配置可见optimization配置的注释部分代码,需自行拉取所需依赖并引入。
关于MiniCssExtractPlugin插件的具体参数,我这里就不做介绍了,可去npm上自行了解。
补充
关于postcss-loader,是为了给一些css3代码加浏览器兼容前缀,所以在目录中创建了一个postcss.config.js配置文件,具体配置内容如下:
module.exports = {
plugins: [
require('autoprefixer')
]
}
Vue相关代码
main.js
import "core-js/modules/es.promise";
import "core-js/modules/es.array.iterator";
import Vue from 'vue'
import router from '@/router/index'
import App from '@/pages/app'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
//热更新,如果更改业务代码,无刷新自动局部更新视图
if (module.hot) {
module.hot.accept()
}
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router';
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/',
component: () => import(
/* webpackChunkName: 'pages/home/index' */
'@/pages/home')
}
]
})
export default router
pages/app.vue
<style lang="scss">
.index {
color: red;
}
</style>
<template>
<section class="index">
<router-view></router-view>
</section>
</template>
<script>
export default {
name: 'index',
data () {
return {
}
}
}
</script>
pages/home/index.vue
<style lang="scss">
.home {
color: blue;
}
</style>
<template>
<section class="home">{{content}}</section>
</template>
<script>
export default {
name: 'home',
data () {
return {
content: 'home页面内容'
}
}
}
</script>
结语
猜你爱看
微 博:前端吴佳
关注「前端技术专栏」加星标
每天给您推送最新原创技术文章
好看,帮点击在看❤️