前序
经过上一篇webpack(基础篇)的学习,已经对webpack有了基本的认识,这里对基础篇进行一个回顾和总结:
作为前端模块话管理工具
webpack的重要核心就是入口(entry)、出口(output)、模块转换器(loader)、插件(plugin)
他主要目的就是,结合loader、plugin将我们项目进行转换打包,将源代码变成一个可以向下兼容版本、体积更小、内容可控可转变的符合生产环境部署的前端资源;
当然他的功能也不只如此,他大大加大了我们在开发时的效率,无疑是前端开发中不可缺少的一环;
出了了解其功能以外还要了解其怎么去配置
查看当前项目的相关依赖包、执行脚本等等信息:package.json
核心配置文件webpack.config.js
loader的一些配置文件例如:babel.config.json、postcss.config.js
重要的一些loader包:babel-loader、style-loader、css-loader、less-loader等等
重要的一些plugin包:html-webpack-plugin、webpack-dev-plugin、clean-webpack-plugin等等
如果忘记了上面这些,就再会基础篇再好好回顾回顾;
接下来我们进入下一篇:webpack(进阶篇)
本篇的核心
通过上篇只是做到对webpack的一个基础的了解,要想要真正的用好webpack还远远不够,上一篇还有不少配置没有讲到,这篇会有补充,此外是更深的介绍webpack的一些相关插件和配置,让我们在平时项目开发中能做对不同项目按需真正去进行webpack使用
ps:
- 当然我也是看着网上大佬的文档来写的,过程中会结合自己的思考输出的文档,勿喷!
- 下面plugin的配置属性会根据版本的迭代发生改变,也就是说有可能按照我文档中的配置项来不一定就ok,有可能安装的包版本和我用的不一样,例如copy-webpack-plugin这个包不同版本的配置发生了改变,所以具体的配置还是要参考对应版本的文档(我就是在网上一些文档配置后打包报错,看了官方文档才发现配置变了),总而言之最好根据官方文档去进行配置,这里只提供一个demo!
- 不要只看文档,要实际操作一波,说不定能发现我写的一些不对的地方,也欢迎指出讨论
一、静态资源拷贝 -- copy-webpack-plugin
有时候你想要webpack不去编译某个js文件、css文件等,想要他在打包的时候就直接原封不动的使用时,例如,我们在 public/index.html 中引入了 某个相对路径文件,打包构建后时绝对找不到这个文件的,除非你手动的再添加这个对应文件(垃圾操作不可取,哪有人去动dist),这时候copy-webpack-plugin就出场了
作用
copy-webpack-plugin可以帮我们指定那些文件不需要编译直接复制过去
配置方式
引入
// npm引入 npm install copy-webpack-plugin -D // yarn引入 yarn add copy-webpack-plugin -D
配置
ps:注意不同的copy-webpack-plugin的配置有些差别的呀,要去看具体文档的配置,别以为你跟着下面这个配置项配就没问题,下面我用的是@11.0.0版本的copy-webpack-plugin
const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { ..., plugin: [ new CopyWebpackPlugin({ patterns: [ { from: 'public/js/*.js', // 指定要拷贝的文件 to: 'js/[name][ext]', // 拷贝到哪个位置 globOptions: { ignore: ['**/other.js'] // 忽略掉哪一些不需要处理的文件 } } ] }) ] }
更多配置参考:CopyWebpackPlugin | webpack 中文文档
全局定义plugin -- ProvidePlugin
当你每个文件都要引用vue、react、jquery这些时,每次都要先进行import或require,这就显的很麻烦了,这时候ProvidePlugin就出场了;
作用
ProvidePlugin可以让我们做一些全局的定义,让我们不需要import或require就可以直接去使用某某plugin
ps:默认寻找路径是当前文件夹 ./** 和 node_modules
配置方式
const webpack = require('webpack'); module.exports = { ..., plugins: [ new webpack.ProvidePlugin({ React: 'react', Component: ['react', 'Component'], Vue: ['vue/dist/vue.esm.js', 'default'], $: 'jquery', _map: ['lodash', 'map'] }) ] }
通过上面的配置后每个页面都不用再去引入下面这些
import React, { Component } form 'react'; import Vue from 'vue/dist/vue.esm.js'; import $ from 'jquery'; import {map} as _map from 'lodash';
ps:不要什么都图方便去进行全局定义,尤其是多人开发的项目,容易造成变量污染问题。。。
抽离CSS -- mini-css-extract-plugin
当一个css文件太大了,打包成一个js文件太大了,文件太大影响影响加载速度,或者为了开发环境下为了缓存(只有js改动时),减少加载或构件时间,我们就可以把css单独打包,这时min-css-extract-plugin就出场了;
作用
其目的当然就是为了让css能够单独进行打包;
当然还有一个叫 extract-text-webpack-plugin 的插件也能对指定文件进行单独打包;mini-css-extract-plugin与他的区别在于:
- mini-css-extract-plugin 是异步加载
- mini-css-extract-plugin 不会重复编译,性能更好
- mini-css-extract-plugin 只适用与 CSS
总而言之webpack4.0以后,官方推将使用 mini-css-extract-plugin,它更快、更好就是了
ps:一般开发环境,css文件我们不会去对其进行压缩,但是生产环境为了减少包的体积,我们会压一下,这时候就需要一个插件 optimize-css-assets-webpack-plugin
配置方式
引入
// npm引入 npm install mini-css-extract-plufin optimize-css-assets-webpack-plugin -D // yarn引入 yarn add mini-css-extract-plufin optimize-css-assets-webpack-plugin -D
配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin'); module.exports = { module: { rules: [ { test: /\.(le|c)ss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'], exclude: /node_modules/ } ] }, plugins: [ new OptimizeCssPlugin(), new MiniCssExtractPlugin({ filename: 'css/[name].css' //个人习惯将css文件放在单独目录下 }), ] }
更多配置参考文档:MiniCssExtractPlugin | webpack 中文文档
按需加载js文件 -- @babel/plugin-syntax-dynamic-import
有的时候我们不想要页面一开始就把所有静态资源都一次性加载出来,有时候一个静态支援根本在有可能用到的场景太少,加载了缺不用,有点浪费,此时就需要到@babel/plugin-syntax-dynamic-import
作用
可以让我们在有需要到对应js文件的时候再去调用加载,从而提高运行效率
配置方法和使用方法
引入
// npm引用 npm install @babel/plugin-syntax-dynamic-import -D // yarn引用 yarn add @babel/plugin-syntax-dynamic-import -D
配置
在对应的babel配置中的plugins中加入 @babel/plugin-syntax-dynamic-import
{ "presets": ["@babel/preset-env"], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ], "@babel/plugin-syntax-dynamic-import" ] }
使用方法
通过import()
方法在特定时机再去加载对应的js文件
document.getElementById('importBtn').onclick = function() { import('./demo.js').then(fn => fn.default()); }
webpack 遇到 import(****) 这样的语法的时候,会这样处理:
- 以**** 为入口新生成一个 Chunk
- 当代码执行到 import 所在的语句时,才会加载该 Chunk 所对应的文件(如这里的758.js)
大家可以在浏览器中的控制台中,在 Network 的 Tab页 查看文件加载的情况,只有点击之后,才会加载对应的 JS 。
更多信息参考文档:@babel/plugin-syntax-dynamic-import · Babel
热更新 -- HotModuleReplacementPlugin
之前我们在了解devServer配置时里面有一个 hot
属性,当设置为true时就可以开启热加载模块的属性,让webpack重新编译,但是页面在代码变更后并没有发生任何改变,要进行手动刷新,很麻烦,这时候就需要HotModuleReplacementPlugin
作用
HotModuleReplacementPlugin是webpack的一个属性,可以让我们整个页面热更新
配置方法
//webpack.config.js const webpack = require('webpack'); module.exports = { //.... devServer: { hot: true }, plugins: [ new webpack.HotModuleReplacementPlugin() //热更新插件 ] }
此时你会发现你一修改代码保存后,webpack就重新编译,然后页面就会刷新,那么另外一个问题就来了,有时候想要某个文件不触发热更新,你可以在对应文件下加如下配置
if(module && module.hot) { module.hot.accept() }
多页面应用
之前我们了解到的都是单页面应用配置,那如果是想搞多页面应用怎么整
配置方法
我们只需要在入口配置那里做好配置,同时增加两个HtmlWebpackPlugin配置,分别指向对应的文件html文件,即可
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { index: './src/index.js', login: './src/login.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[hash:6].js' }, //... plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', filename: 'index.html', //打包后的文件名 chunks: ['index'] //指定的js引入到html文件中 }), new HtmlWebpackPlugin({ template: './public/login.html', filename: 'login.html', //打包后的文件名 chunks: ['login'] //指定的js引入到html文件中 }), ] }
resolve 配置
作用
resolve配置能让我们在引入(import)对应的模块时写法更加的优雅,同时还能制定引用的优先级,防止我们引用错误
配置参数说明
modules
resolve.modules 配置 webpack 去哪些目录下寻找第三方模块,默认情况下,只会去 node_modules 下寻找,如果你我们项目中某个文件夹下的模块经常被导入,不希望写很长的路径,那么就可以通过配置 resolve.modules 来简化。
module.exports = { //.... resolve: { modules: ['./src/components', 'node_modules'] //从左到右依次查找 } }
这样配置之后,我们 import Dialog from ‘dialog’,会去寻找 ./src/components/dialog,不再需要使用相对路径导入。如果在 ./src/components 下找不到的话,就会到 node_modules 下寻找。
alias
resolve.alias 配置项通过别名把原导入路径映射成一个新的导入路径,例如:
module.exports = { //.... resolve: { alias: { 'react-mahk': '@mahk/react-mahk-util' //包随便写的哈 } } }
例如,我们有一个依赖 @mahk/react-mahk-util 可以实现 react-mahk 转 web。我们代码一般下面这样:
配置了别名之后,在转 web 时,会从 @mahk/react-mahk-util 寻找对应的依赖。
当然啦,如果某个依赖的名字太长了,你也可以给它配置一个短一点的别名,这样用起来比较爽,尤其是带有 scope 的包。
extensions
适配多端的项目中,可能会出现 .web.js, .wx.js,例如在转web的项目中,我们希望首先找 .web.js,如果没有,再找 .js。我们可以这样配置
module.exports = { //.... resolve: { extensions: ['web.js', '.js'] //当然,你还可以配置 .json, .css } }
首先寻找 …/dialog.web.js ,如果不存在的话,再寻找 …/dialog.js。这在适配多端的代码中非常有用,否则,你就需要根据不同的平台去引入文件(以牺牲了速度为代价)。
同时当然,配置 extensions,我们就可以缺省文件后缀,在导入语句没带文件后缀时,会自动带上extensions 中配置的后缀后,去尝试访问文件是否存在,因此要将高频的后缀放在前面,并且数组不要太长,减少尝试次数。如果没有配置 extensions,默认只会找对对应的js文件。
enforceExtension
如果配置了 resolve.enforceExtension 为 true,那么导入语句不能缺省文件后缀。
mainFields
有一些第三方模块会提供多份代码,例如 bootstrap,可以查看 bootstrap 的 package.json 文件:
{ "style": "dist/css/bootstrap.css", "sass": "scss/bootstrap.scss", "main": "dist/js/bootstrap", }
esolve.mainFields 默认配置是 [‘browser’, ‘main’],即首先找对应依赖 package.json 中的 brower 字段,如果没有,找 main 字段。
如:import ‘bootstrap’ 默认情况下,找得是对应的依赖的 package.json 的 main 字段指定的文件,即 dist/js/bootstrap。
假设我们希望,import ‘bootsrap’ 默认去找 css 文件的话,可以配置 resolve.mainFields 为:
module.exports = { //.... resolve: { mainFields: ['style', 'main'] } }
区分不同的环境
之前我们一直是什么配置都在webpack.config.js上配置,但是一个成熟的项目生产环境的配置项和开发环境的配置项肯定是不同的呀,所以一般我们会创建多个配置文件,再按需合并多个配置项,例如:
- webpack.common.js //定义一些公共的配置
- webpack.dev.js // 定义一些开发环境专属的配置
- webpack.pro.js // 定义一些生产环境专属的配置
最后在根据环境变量把这些按需组合起来,而组合就要用到webpack-merge
webpack-merge
作用
用于把多个webpack配置项合并起来
使用方式
引入
// npm引入 npm install webpack-merge -D // yarn引入 yarn add webpack-merge -D
使用
通过使用merge()
方法进行合并,例如
const merge = require('webpack-merge'); merge({ devtool: 'cheap-module-eval-source-map', module: { rules: [ {a: 1} ] }, plugins: [1,2,3] }, { devtool: 'none', mode: "production", module: { rules: [ {a: 2}, {b: 1} ] }, plugins: [4,5,6], }); //合并后的结果为 { devtool: 'none', mode: "production", module: { rules: [ {a: 1}, {a: 2}, {b: 1} ] }, plugins: [1,2,3,4,5,6] }
ps:当然在webpack4后也可以使用merge.smart()
进行配置项合并
定义环境变量 -- DefinePlugin
一般我们业务代码中有时候会感觉根据不同的环境去调用不同的指,此时就需要DefinePlugin去定义全局的环境变量,因为涉及全局,还是那句话,全局变量要慎重,容易造成变量污染(不对,它是常量!!!不是变量😢)
如何进行配置和定义环境变量
DefinePlugin 中的每个键,是一个标识符.
- 如果 value 是一个字符串,会被当做 code 片段
- 如果 value 不是一个字符串,会被stringify
- 如果 value 是一个对象,正常对象定义即可
- 如果 key 中有 typeof,它只针对 typeof 调用定义
const webpack = require('webpack'); module.exports = { plugins: [ new webpack.DefinePlugin({ DEV: JSON.stringify('dev'), //字符串 FLAG: 'true' //FLAG 是个布尔类型 }) ] }
if(DEV === 'dev') { //开发环境 }else { //生产环境 }
webpack.DefinePlugin与cross-env区别
- webpack.DefinePlugin 用于在编译期定义环境变量,意味着在代码中写上 process.env.NODE_ENV 不会在编译期出现错误提醒;
- cross-env 库用于在运行时定义环境变量
- 问题场景:为什么要使用 cross-env 库?
-
- 在进行“NODE_ENV=development webpack”配置时候,在大多数Windows命令行中在使用NODE_ENV = production设置环境变量时会报错。同样Windows和Linux命令如何设置环境变量也有所不同。所以需要使用 cross-env 库来支持跨平台设置和使用环境变量的脚本,这样可以设置在不同的平台上有相同的NODE_ENV参数。
- DefinePlugin 用途:根据不同的环境进行不同的配置,如不同环境的域名不同,我们就可以利用 DefinePlugin 进行配置
参考文档