vlambda博客
学习文章列表

手把手带你使用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']  }
resolve配置主要配置alias or extensions,它们分别作用:
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() ]})
生产配置我就不一一讲解了,这里我就只说重要部分,当然重要的部分就是对于一些压缩和优化上的操作,并且生产环境是不需要服务的,它与开发环境最大的区别就是生产环境会分割代码,分离css,压缩代码,做一些优化上的处理,而开发环境是不会特意做这些操作的。
首先,它和开发配置一样,把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>



结语


以上是本文全部内容,如有错误请留言指正,共同讨论,一起进步。
如需尝试运行,请自行去仓库拉取代码,地址:
https://github.com/wujiabk/vue-dev-environment



猜你爱看







微  博:前端吴佳






关注「前端技术专栏」加星标

每天给您推送最新原创技术文章



好看,帮点击在看❤️