手把手带你使用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将图片转换成base64name: '[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: ''}),//提取cssnew MiniCssExtractPlugin({filename: 'assets/style/[name].[chunkhash:8].css',chunkFileName: 'assets/style/[name].[chunkhash:8].css',allChunks: true}),//删除多余的CSSnew 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 = falsenew 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>
结语
猜你爱看
微 博:前端吴佳
关注「前端技术专栏」加星标
每天给您推送最新原创技术文章
好看,帮点击在看❤️
