webpack
webpack:模块打包器、模块加载器、代码拆分、载入资源模块
webpack.config.js文件是运行在nodejs文件下的js文件,我们需要按照CommonJS的方式编写代码。这个文件需要导出一个对象,我们完成对应的配置选项。
webpack常用命令参数
可以在命令行直接使用,或者在package.json文件中的scripts中添加对应的命令 www.webpackjs.com/api/cli/
--config my-config.js 用来自定义webpack的配置文件名称
--watch 监听模式
--env 配置环境参数 development production
--mode 模式 none | development | production
webpack的基本使用
const path = require('path')
module.exports = {
watch:false, //是否开启监听模式
mode:'development', //开启开发者模式
devtool:false, //此选项控制是否生成,以及如何生成 source map。
context:'',//默认就是执行当前文件的命令行文件所在的路径,这个和process.cwd()方法获取路径的方式保持一致,
entry:'./src/index.js', //这个没有路径问题,因为这时候的entry路径前面会拼接context上下文路径,而context上下文路径是以执行当前文件的命令行路径为基准的。指定打包入口文件,如果是相对路径,前面的点不能少,如果传递了一个字符串或字符串数••组,则该块被命名为main。如果传递了一个对象,则每个键都是一个块的名称,值描述了该块的入口点。
output: { //https://webpack.js.org/configuration/output/
filename: 'bundle.js', // 输出文件的名称
path: path.join(__dirname, 'output'), // 输出路径,为绝对路径
publicPath:'', //打包时候代表静态资源存放服务器的地址,开发服务器的时候index.html和静态资源存放的内存地址
},
resolve:{}, //解析,具体常用配置可看下方的resolve解析
devServe:{}, //开发环境配置
optimization:{}, //优化
externals:{}, //外部扩展
module:{
rules:[], //loader的处理规则
},
plugins:[], //插件
target:{}, //https://webpack.js.org/configuration/target/ webpack可以针对多个目标或环境进行编译
Performance:{}, //https://webpack.js.org/configuration/performance/ 这些选项允许您控制 webpack 如何通知您超出特定文件限制的资产和入口点
}
依赖路径
webpack中一切皆模块,如果想要这个文件能够被webpack处理,那这时候这个文件就需要被入口文件或者被入口文件所引用的其他文件所引用,然后这些不同的文件被不同的loader处理
loader
为什么需要loader?
loader从字面的意思理解,是 加载 的意思。 由于webpack 本身只能打包commonjs规范的js文件,所以,针对css,图片等格式的文件没法打包,就需要引入第三方的模块进行打包。loader虽然是扩展了 webpack ,但是它只专注于转化文件(transform)这一个领域,完成压缩,打包,语言翻译。loader是运行在NodeJS中。仅仅只是为了打包,仅仅只是为了打包,仅仅只是为了打包,重要的话说三遍!
如:css-loader和style-loader模块是为了打包css的
babel-loader和babel-core模块时为了把ES6的代码转成ES5
url-loader和file-loader是把图片进行打包的。
loader是什么?
loader就是用于处理不同类型文件(或者说是模块)的插件
loader是一个模块
使用loader
1 行内loader,在引入对应模块的地方,使用对应loader
import 'css-loader!../css/login.css'
function login(name){
let h = document.createElement('h1');
h.innerText = name
h.className = name
return h
}
login('欢迎学习webpack5')
2 配置文件中添加loader
module:{
rules:[ //loader的执行顺序,永远是从数组的最后位置往前执行
// 第一种
{
test:/.css$/,
loader:'style-loader'
},
{
test:/.css$/,
loader:'css-loader'
},
//第二种
{
test:/.css$/,
rules:[
{
loader:"css-loader",
options:{}
},
]
},
// 第三种,有点相当于前两种的综合体
{
test:/.css$/,
use:[
"style-loader",
{
loader:"css-loader",
options:{}
}
]
}
]
}
loader的执行顺序,永远是从数组的最后位置往前执行 3 loader的实现
实现一个markdown的文件加载器。
Loader作为webpack的核心机制,内部的工作原理也非常简单,我们通过开发一个自己的loader,来深入了解loader的工作原理。
我们的需求是实现一个markdown文件的加载器,这个加载器可以在代码当中直接导入markdown文件。markdown文件一般是被转换为html过后再去呈现到页面上的,所以我们导入的markdown文件得到的结果就是转换过后的html字符串。
在项目的根目录下新建一个markdown-loader.js文件,每一个webpack的loader都需要去导出一个函数,这个函数就是我们这个loader的对我们所加载到的资源的一个处理过程,它的输入就是我们资源文件的内容,输出就是我们此次加工过后的一个结果。
那我们通过source参数去接收输入,然后通过我们的返回值去输出,我们先尝试打印一下这个source,然后直接去返回一个hello,我们去看一下结果,我们回到webpack配置文件中去添加一个加载新的规则配置,我们匹配到的扩展名就是.md,就是我们刚刚所编写的markdown-loader的模块,我们的use属性不仅仅只可以使用模块的名称,其实对于模块的文件路径也是可以的,这一点其实与node当中的require函数是一样的,所以直接使用相对路径去找到这个markdown-loader,配置好过后,运行打包命令,打包过程当中命令行确实打印出来了我们所导入的markdown的内容,那这也就意味着我们的source确实是所导入的文件内容,但是它同时也爆出一个解析错误,说的是you many need additional load to handle the result of this loader,就是我们还需要一个额外的加载器来去处理我们当前的加载结果,那这究竟是为什么呢?
module.exports = source => {
console.log(source)
return 'hello'
}
其实webpack的加载资源的过程有点类似于一个工作管道,你可以在这个过程当中一次去使用多个loader,但是,还要求我们最终这个管道工作过后的结果必须是一段JavaScript代码,因为我们这返回的内容是一个hello,它不是一个标准的JavaScript代码,所以我们这才会出现这样的错误提示,那知道这个错误的原因过后,解决的办法其实也就很明显了,那要么就是我们这个loader的直接去返回一段标准的JavaScript代码,要么就是我们再去找一个合适的加载器,接着去处理我们这里返回的结果。
我们先来尝试第一种办法。回到我们markdown-loader.js的当中,我们将返回的这个内容修改为'console.log("hello")',那这就是一段标准的JavaScript代码,然后我们再一次运行打包,那此时打包过程当中就不再会报错了。
module.exports = source => {
console.log(source)
return 'console.log("hello")'
}
接下来我们一起来看一下打包过后的结果究竟是什么样的,我们打开bundle.js当中,然后我们找到最后一个模块,可以看到,webpack打包的时候就是把我们刚刚loader加载过后的结果也就是返回的那个字符串直接拼接到我们这个模块当中了,那这也就解释了刚刚为什么说loader的管道最后必须要去返回JavaScript代码的原因,因为如果说你随便去返回一个内容的话,那放到这里语法就有可能不通过。
那知道了这些过后,我们再回到markdown-loader.js的当中,然后接着去完成我们刚刚的需求,我们先去安装一个markdown解析的模块叫做marked,安装命令为:yarn add marked --dev
安装完成过后,我们再回到代码当中去导入这个模块。然后在我们的加载器当中去使用这个模块,去解析来自参数当中的这个source,我们的返回值就是一段html字符串,也就是转换过后的结果,但是如果直接返回这个html的话,那就会面临刚刚同样的问题,正确的做法就是把这段html变成一段JavaScript代码,其实我们希望是把这一段html作为我们当前这个模块导出的字符串,也就是我们希望通过export导出这样一个字符串,但是如果说我们只是简单的拼接的话,那我们html当中存在的换行符还有它内部的一些引号,拼接到一起就有可能造成语法上的错误,所以说这里我使用一个小技巧,就是通过JSON.stringify先将这个字符串转换为一个标准的JavaScript格式字符串,那此时内部的引号以及换方符都会被转义过来,然后我们再参与拼接,那这样的话就不会有问题了,我们再次运行打包,看一下打包的结果,那此时我们所看到的结果就是我们所需要的了,当然了,除了module.exports这种方式有外,webpack的还允许我们在返回的代码当中直接去使用ESModule的方式去导出.
const marked = require('marked')
module.exports = source => {
// console.log(source)
// return 'console.log("hello")'
const html = marked(source)
console.log(html)
// 两种导出方式:
// return `module.exports=${JSON.stringify(html)}`
return `export default ${JSON.stringify(html)}`
}
通过第一种方式解决了我们刚刚所看到的那样一个错误,我们再来尝试一下刚刚所说的第二种方法,那就是在我们markdown-loader的当中去返回一个html的字符串,然后我们交给下一个loader处理这个html的字符串,我们直接去返回marked的解析过后的html,然后我们再去安装一个用于去处理html加载的loader,叫做html-loader,完成过后,我们回到配置文件当中,我们把use属性修改为一个数组,那这样的话我们的loader工作过程当中就会依次去使用多个loader,那不过这里需要注意,就是它的执行顺序是从数组的后面往前面,那也就是说我们应该把先执行的loader放到后面,后执行的loader放到前面。
const marked = require('marked')
module.exports = source => {
// console.log(source)
// return 'console.log("hello")'
const html = marked(source)
console.log(html)
return html
}
module: {
rules: [
{
test: /.md$/,
use: ['html-loader', './markdown-loader.js']
}
]
}
回到命令行进行打包,此时我们打包的结果仍然是可以的,我们marked处理完的结果是一个html的字符串,然后这个html字符串交给了下一个loader,也就是html-loader,那这个loader又把它转换成了一个导出这个字符串的一个JavaScript代码,那这样的话我们webpack再去打包的时候就可以正常工作了。
那通过以上的这些尝试我们就发现了loader它内部的一个工作原理其实非常简单,就是一个从输入到输出之间的一个转换,那除此之外,我们还了解了loader,它实际上是一种管道的概念,我们可以将我们此次的这个loader的结果交给下一个loader去处理,然后我们通过多个loader去完成一个功能,那例如我们之前所使用的css-loader和style-loader之间的一个配合,包括我们后面还会使用到的,像sass或者less这种loader他们也需要去配合我们刚才所说到的这两种loader,这就是loader的工作管道这样一个特性。
loader处理不同类型的模块,到最后的返回值必须是一个符合es语法和esModule的模块语法。
browserslistrc
1 项目工程化需求
2 需要兼容CSS jS
3 如何实现兼容:babel postCss
4 到底要兼容哪些平台可以通过can1use.com这个网站查找各个平台占有率,然后根据条件进行兼容
npx browserslist || yarn browserslist 会走默认配置,返回对应的浏览器平台
1% last 2 versions not dead
自定义配置
1 package.json文件中添加
"browserslist":[
">1%",
"last 2 version",
"not dead"
]
2 根目录下创建 .browserslistrc文件
> 1%
last 2 versions
not dead
babel
www.babeljs.cn/ 处理像js,jsx等兼容性的代码
核心包 @babel/core 单独使用需要安装@babel/cli
npx babel script.js //编译文件
npx babel script.js --out-file script-compiled.js //输出到文件 --out-file 或 -o 参数。
npx babel script.js --watch --out-file script-compiled.js //文件修改后 --watch 或 -w 参数
npx babel src --out-file script-compiled.js //编译整个 src 目录下的文件并输出到 lib 目录,输出目录可以通过 --out-dir 或 -d 指定。这不会覆盖 lib 目录下的任何其他文件或目录。
npx babel src --out-dir lib --ignore "src/**/*.spec.js","src/**/*.test.js" //忽略某些文件
npx babel src --out-dir lib --copy-files //复制不需要编译的文件
npx babel script.js --out-file script-compiled.js --plugins=@babel/proposal-class-properties,@babel/transform-modules-amd //使用插件
npx babel script.js --out-file script-compiled.js --presets=@babel/preset-env,@babel/flow //使用 Preset
webpack中使用安装babel-loader
@babel/preset-env
@babel/preset-env用来转换es语法,
polyfill
对于api和一些表达式就必须使用polyfill webpack4中内置了polyfill,所以我们并不需要手动引入,但是因为它是全局引入,所以有些内容我们可能是不需要的,所以到了webpack5,就给移除掉了,需要我们手动引入!
核心包:@babel/polyfill 这个是兼容所有api和表达式的包 而这其中其实我们只需要安装下面的两个core-js/stable和regenerator/runtime
npm i -S core-js & npm i -S regenerator
配置方式
babel-loader
//单独一个一个的插件
{
test:/.js$/,
exclude:/node_modules/, //排除掉指定路径下的文件
use:[
{
loader:'babel-loader',
options:{
plugins:[
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-block-scoping'
],
}
}
]
}
//插件的集合presets
{
loader:'babel-loader',
options:{
presets:[
[
"@babel/preset-env",
{
"targets":"chrome 102" //设置要兼容的浏览器版本,这里可以单独配置,如果不配置,那就走browserslistrc的配置或者默认配置项
}
]
]
}
}
* babel-loader 相关的单独配置文件
* babel.config.js(json cjs mjs)
* babelrc.json(js)
module.exports = {
// plugins:[
// '@babel/plugin-transform-arrow-functions',
// '@babel/plugin-transform-block-scoping'
// ],
presets:[
// [
// "@babel/preset-env",
// {
// "targets":"chrome 102"
// }
// ],
[
"@babel/preset-env",
{
// "targets":"chrome 102"
/*
false: 不对当前的js处理做polyfill的填充
usage: 会自动检查用户所使用到的新api或表达式进行兼容
entry: 根据browserslistrc配置文件所查找出来的浏览器进行兼容
需要在使用到的地方手动引入
import "core-js/stable";
import "regenerator-runtime/runtime";
*/
"useBuiltIns":'entry',
"corejs":3 //当babel执行的时候默认使用的是2的版本,而我们现在安装的是3的版本,所以需要我们手动指定版本
}
]
]
}
postCss
autoprefixer.github.io/ 帮助我们使用javascript对css进行转换
核心包:postcss 单独使用需要安装postcss-cli webpack中使用安装postcss-loader
当我们需要对css文件进行兼容时候
* PostCSS: v8.4.12, 核心的转换插件
* Autoprefixer: v10.4.4 对css进行兼容处理 需要单独安装 autoprefixer
* Browsers: last 4 version 要兼容的浏览器版本 这个需要安装browserslist
* postcss-preset-env postcss所使用的到的一系列插件的集合
自定义配置
1 在webpack.congig.js文件中配置
{
loader:'postcss-loader',
options:{
postcssOptions:{
plugins:[
require('autoprefixer'), //处理css兼容的插件
require('postcss-preset-env') //很多插件的集合
]
}
}
},
2 根目录下创建postcss.config.js文件导出参数
当我们使用postcss插件需要配置多个地方的时候,就可以单独配置一个postcss.config.js文件了
module.exports={
plugins:[
require('autoprefixer'),
require('postcss-preset-env')
]
}
importLoaders
需求当我们使用postcss对css代码进行一些兼容性处理时,因为postcss是不支持@import,media,url...,所以对于css中使用这些语法引入的css是无法处理的,所以这时候想要这些动态引入的css文件也支持此功能,就需要使用css-loader来处理,css-loader是支持此功能的
加配置参数
{
test:/.css$/,
loader:'css-loader',
options:{
importLoaders:1 //这个配置参数代表,如果css-loader处理的文件中出现了通过@import加载的文件,那这时候这个文件还需要上一层的postcss-loader处理,那这时候就需要配置此参数,参数类型为数字,当处理文件需要之前的几个loader在重新处理,那这里的数字就是几,这里只需要被postCss-loader处理,所以写1
}
},
TypeScript
核心包:typescript webpack上使用需要安装ts-loader
npm i -D ts-loader
{
module:{
rules:[
{
test:/.ts$/,
use:[
'ts-loader'
]
}
]
}
}
但是ts-loader只能处理一些简单语法,对于api的兼容还是需要配置babel-loader
// 配置文件上
(1) 第一种方案,既可以处理ts语法,也可以处理js语法和api,但是ts报的错误不会影响最终的打包结果
{
module:[
rules:[
{
test:/.ts$/,
exclude:/node_modules/,
use:[
'babel-loader', //babel-loader配合@babel/preset-env可以处理语法,如果需要处理最新api和一些表达式,那这时候就要下载core-js和regenerator,然后在babel.config.js中配置
'ts-loader', //先通过ts-loader处理ts语法,然后在交给babel-loader
]
},
]
]
}
//也可以安装@babel/preset-typescript
(2) 第二种方案
package.json
{
"scripts":{
"ts":"tsc --noEmit", //用tsc去校验ts语法 --noEmit就是不输出文件
"build":"webpack",
"builds":"npm run ts && npm run build"||"tsc --noEmit && webpack",//这种方式当ts校验不通过后续的代码将不会执行
}
}
babel.config.js
module.exports = {
presets:[
[
'@babel/preset-env',
{
'useBuiltIns':"usage"
'corejs':3
}
],
"@babel/preset-typescript"
]
}
/*
请注意,如果您已经在使用 babel-loader 转译您的代码,您可以使用 @babel/preset-typescript 并让 Babel 处理您的 JavaScript 和 TypeScript 文件,而不是使用额外的加载器。 请记住,与 ts-loader 不同,底层的 @babel/plugin-transform-typescript 插件不执行任何类型检查。
*/
处理图片
file-loader,url-loader
/*处理图片文件 file-loader,默认把要打包的图片通过一些配置后,拷贝到打包目录,分开请求
img src文件
1 使用require导入图片,返回一个esModule对象,这时候图片地址需要手动.default获取
2 也可以在webpack配置文件使用file-loader时候加上配置参数 esModule:false
3 或者直接使用import *** from 图片资源,此时可以直接使用 ***
background url
css-loader 处理到url的时候也是通过require来进行加载,但是返回的是一个对象,所以我们需要在css-loader的配置项中加上esModule:false
*/
{
test:/.(png|jpe?g|gif|svg)/,
use:[
{
loader:'file-loader',
options:{
esModule:false, //代表不使用esModule方式进行导入模块
name:'./img/[name].[hash:6].[ext]', //输出目录img,或者下面这种方式
// outputPath:'img'
}
}
]
}
//处理图片文件 url-loader,默认把要打包的图片转换成base64打包到代码中,减少请求次数。url-loader内部其实也可以调用file-loader,增加配置limit
{
test:/.(png|jpe?g|gif|svg)/,
use:[
{
loader:'url-loader',
options:{
limit:1000*1024, //大小限制,当图片的大小小于这个时,转换为base64,大于时,自动调用file-loader
name:'./img/[name].[hash:6].[ext]', //输出目录img,或者下面这种方式
// outputPath:'img'
}
}
]
}
/*
webpack5 asset module type新增的功能支持替换上面两个loader
{
output:{
assetModuleFilename:'./img/[name].[hash:6][ext]', //使用asset时,文件的统一输出目录
},
}
{
test:/.(png|svg|jpe?g|png)$/,
type:'asset/resource',
generator:{
filename:"img/[name].[hash:6][ext]" //当我们对不同的文件类型做处理后要输出到不同的目录下,那这时候就不适用全局的配置,而要使用这种单独的配置方式
}
}
1 asset/resource 相当于file-loader
{
test:/.(png|svg|jpe?g|png)$/,
type:'asset/resource' //相当于使用file-loader,处理文件
},
2 asset/inline 相当于url-loader
{
test:/.(png|svg|jpe?g|png)$/,
type:'asset/inline' //相当于使用url-loader,处理文件后
},
3 asset/source 相当于raw-loader
4 asset 对上面的统一处理
{
test:/.(png|svg|jpe?g|png)$/,
type:'asset', //统一使用,当处理的图片小于我们配置的大小时候,就输出base64,否则输出文件
generator:{
filename:"img/[name].[hash:6][ext]" //输出目录
},
parser:{
dataUrlCondition:{
maxSize:1000*1024 //图片大小
}
}
},
asset处理js中动态加载的图片时,默认就是default模式
*/
//css-loader处理css中加载图片时的处理
{
test:/.css$/,
loader:'css-loader',
options:{
esModule:false, //代表不使用esModule方式进行导入模块,因为css中有可能有通过url导入的图片,这时候它是通过esModule方式导入的,默认需要手动default,加上这个参数后就不需要手动.default
}
}
//HTML代码中的图片标签的src属性
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
// html-loader默认只处理页面中的img标签的src属性的资源文件,所以指定其他标签的资源文件也要处理
attributes: {
list: [
{
tag: 'img',
attribute: 'src',
type: 'src'
},
{
tag: 'a',
attribute: 'href',
type: 'src'
}
]
}
}
}
}
/**
* [ext]:文件扩展名
* [name]:文件名称
* [hash]|[chunkhash]|[contenthash]:都是用来给文件添加唯一值的,但是这三者有所不同
hash
hash是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值
chunkhash
chunkhash根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。
contenthash
假设index.js引用了index.css,若使用chunkhash的话,只要js改变,那么css也会被重新构建。如何防止css被重新构建呢?
可以使用extract-text-webpack-plugin提取单独的css文件并使用contenthash
contenthash只要文件内容不变,那么不会重复构建。
[hash:<length>]|[chunkhash:<length>]|[contenthash:<length>] 这里的length代表截取hash的长度
* [path]:文件路径
* */
处理字体
{
test:/.(ttf|woff2?)$/,
type:'asset/resource', //相当于使用file-loader,直接当静态资源拷贝
generator:{
filename:"img/[name].[hash:6][ext]" //输出目录
},
}
plugin插件
目的在于解决 loader 无法实现的其他事,它直接作用于 webpack,扩展了它的功能。在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出结果。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
plugin完成的是loader不能完成的功能,这是废话,没有说清楚。 plugin也是为了扩展webpack的功能,但是 plugin 是作用于webpack本身上的。而且plugin不仅只局限在打包,资源的加载上,它的功能要更加丰富。从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。webpack提供了很多开箱即用的插件:CommonChunkPlugin主要用于提取第三方库和公共模块,避免首屏加载的bundle文件,或者按需加载的bundle文件体积过大,导致加载时间过长,是一把优化的利器。而在多页面应用中,更是能够为每个页面间的应用程序共享代码创建bundle。 插件可以携带参数,所以在plugins属性传入new实例。
如:针对html文件打包和拷贝(还有很多设置)的插件:html-webpack-plugin。 不但完成了html文件的拷贝,打包,还给html中自动增加了引入打包后的js文件的代码(),还能指明把js文件引入到html文件的底部等等。
// 插件本质上就是一个类
/*
clean-webpack-plugin 可以用来清理文件和文件夹的插件
默认情况下,此插件将output.path在每次成功重建后删除 webpack 目录中的所有文件
npm install --save-dev clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
*/
/*
html-webpack-plugin 可以用来创建html文件的插件
内部会有一个默认配置的模板文件,但是也可以用自己自定义的文件作为模板
npm i --save-dev html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
new HtmlWebpackPlugin() //走插件的默认配置
new HtmlWebpackPlugin({ //自定义title
title:'my webpack app'
})
new HtmlWebpackPlugin({ //直接使用自定义模板
title:"my webpack app",
template:"./public/index.html"
})
new HtmlWebpackPlugin({ //直接使用自定义模板
chunks:['shop'] //当webpack有多个入口时,默认情况下多个入口的文件都会被当前模板插件所引用,所以这时候可以通过配置这个参数,来决定使用哪几个入口,[‘这里的名称要和入口名称相同’]
})
inline-chunk-html-plugin 将脚本块内联到index.html
npm i --save-dev inline-chunk-html-plugin
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/.js$/][哪些js文件要内联写入html中,这里的规则是所有以.js结尾的文件写入html中]),
*/
/*
DefinePlugin 定义项目中所需要使用的变量
允许在 编译时 将你代码中的变量替换为其他值或表达式
const { DefinePlugin } = require('webpack'); webpack的内置插件
new DefinePlugin({ //允许在 编译时 将你代码中的变量替换为其他值或表达式
BASE_URL:"'./'", //定义时的变量值都必须是JSON
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
})
在为 process 定义值时,'process.env.NODE_ENV': JSON.stringify('production') 会比 process: { env: { NODE_ENV: JSON.stringify('production') } } 更好,后者会覆盖 process 对象,这可能会破坏与某些模块的兼容性,因为这些模块会在 process 对象上定义其他值。
请注意,由于本插件会直接替换文本,因此提供的值必须在字符串本身中再包含一个 实际的引号 。通常,可以使用类似 '"production"' 这样的替换引号,或者直接用 JSON.stringify('production')。
*/
/*
copy-webpack-plugin 将已存在的单个文件或整个目录复制到构建目录。
npm install copy-webpack-plugin --save-dev
const CopyPlugin = require("copy-webpack-plugin");
new CopyWebpackPlugin({
patterns:[
{
from:'public', //要复制哪个文件夹下的内容
// to:'dist', //输出目录,但是一般不用配置,默认直接使用output.path
globOptions:{
ignore:['**/index.html'] //在指定的复制文件夹下,要忽略的文件,必须以**开头
}
}
]
})
*/
/*
CSS抽离和压缩
MiniCssExtractPlugin可以提取CSS到单个文件
OptimizeCssAssetsWebpackPlugin 压缩输出的CSS文件
npm install --save-dev mini-css-extract-plugin
npm install --save-dev optimize-css-assets-webpack-plugin
const MiniCssExtracPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
optimization:{
minimize:true,
minimizer:[
new CssMinimizerPlugin()
]
},
module:{
rules:[
{
test:/.css$/,
use:[
env[环境参数] ? MiniCssExtractPlugin.loader : "style-loader",
{
loader:"css-loader",
options:{
esModule:false,
importLoaders:1
}
},
'postcss-loader',
]
},
]
},
plugins:[
new MiniCssExtractPlugin({
filename:"css/[name].[contenthash:8].css"
})
]
}
*/
自定义插件
相比于Loader,Plugin拥有更宽的能力范围,Plugin通过钩子机制实现。
Webpack要求插件必须是一个函数或者是一个包含apply方法的对象。通过在生命周期的钩子中挂载函数实现扩展。
class MyPlugin {
apply (compiler) {
console.log('MyPlugin 启动')
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation 可以理解为此次打包的上下文
for (const name in compilation.assets) {
// console.log(name) // 文件名
console.log(compilation.assets[name].source())
if(name.endsWith('.js')) {
const contents = compilation.assets[name].source()
const withoutComments = contents.replace(//**+//g, '')
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
plugins: [
new MyPlugin()
]
watch模式
你可以指示 webpack "watch" 依赖图中的所有文件以进行更改。如果其中一个文件被更新,代码将被重新编译,所以你不必手动运行整个构建。
package.json
"scripts":{
"watch":"webpack --config [指定的webpack配置文件] --watch"
}
在webpack配置文件中配置
{
watch:true
}
/*
每次文件更改
所有源代码都会被重新编译
每次编译成功之后都需要进行文件读写
需要手动去浏览打包好的文件
不能实现局部刷新
*/
webpack-dev-server
webpack-dev-server 为你提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。让我们设置以下: webpack.js.org/configurati…
npm i -D webpack-dev-server
package.json
"scripts":{
"serve":"webpack serve --config [指定的webpack配置文件]"
}
在webpack配置文件中配置
{
devServer: {
// webpack5
hot:false,
/*
true 开启热更新
only 我们的一个服务可能有很多模块,当我们其中一个模块报错后,这时候页面就会显示报错信息,然后我们根据报错信息更改报错,更改完保存,这时候回去整体刷新页面,所以当我们不需要整体刷新,只需要刷新部分页面的时候,就可以开启当前配置项
*/
compress:true, //开启服务端gizp压缩
client:{
webSocketURL:{
port:9999
}
},
open:true, //是否在服务启动后,帮我们打开浏览器
historyApiFallback:true, //使用HTML5 History API时,index.html可能需要提供页面来代替任何404响应。devServer.historyApiFallback通过将其设置为启用true:通过提供一个对象,可以使用以下选项进一步控制此行为rewrites:
historyApiFallback: {
rewrites: [
{ from: /^/$/, to: '/views/landing.html' },
{ from: /^/subpage/, to: '/views/subpage.html' },
{ from: /./, to: '/views/404.html' },
],
},
//< webpack5
hot:true, //开启热更新
hotOnly:true,//我们的一个服务可能有很多模块,当我们其中一个模块报错后,这时候页面就会显示报错信息,然后我们根据报错信息更改报错,更改完保存,这时候回去整体刷新页面,所以当我们不需要整体刷新,只需要刷新部分页面的时候,就可以开启当前配置项hotOnly
port:4000, //自定义启动服务所在的端口号
open:true, //是否在服务启动后,帮我们打开浏览器
contentBase: './dist',//指定静态资源的路径
compress:true, //开启服务端gizp压缩
},
}
代理Proxy
{
devServer:{
proxy:{ //具体配置可以参考官方文档 https://github.com/chimurai/http-proxy-middleware
'/axios':{
target:"http://cszsdt.niuhh.cn", //需要请求的地址http://cszsdt.niuhh.cn/api/auth
pathRewrite:{
'^/axios':''
},
/*
重写路径
当我们访问http://localhost:8080/axios/api/auth ///api/auth这个相当于是请求参数
这时候代理会将我们的请求
http://cszsdt.niuhh.cn/axios/api/auth 拼接成这样,但是/axios/api/auth 并不是我想要的
这时候需要重写路径,将/axios/api/auth 重写成/api/auth
然后拼接到
http://cszsdt.niuhh.cn/api/auth
*/
// 不能使用localhost:8080作为请求GitHub的主机名
changeOrigin: true, // 以实际代理的主机名去请求
}
}
}
}
HMR
webpack.js.org/guides/hot-… webpack.js.org/configurati…
{
devServer: {
contentBase: './dist',
hot: true, //开启热更新功能,或者使用new webpack.HotModuleReplacementPlugin()这个插件
},
}
配置完上述参数后,这时候并不是热更新局部模块,还是全局刷新,这时候需要在我们的入口文件添加
// Webpack中的HMR并不是对所有文件开箱即用,样式文件支持热更新是因为这时候对css处理的loader已经处理过了,而脚本文件则没有,所以需要我们手动处理模块热替换逻辑。而通过脚手架创建的项目内部都集成了HMR方案。
if(module.hot){
//['./utils/common.js'] 这个里面就是要监听的热更新文件
module.hot.accept(['./utils/common.js'],([src])=>{ //这里的数组和上一个是一一对应关系
console.log('热更新');
})
或者
module.hot.accept('./src/home.js',src => { //src就是当前元素的变化
//监听到对应文件变化后的回调
})
}
react下的热更新
首先要保证webpack的devServer.hot=true
下载下面两个插件
npm i -D @babel/preset-react
npm i -D @pmmmwh/react-refresh-webpack-plugin
npm i -D react-refresh
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
在webpack的配置中添加
module.exports = {
module:{
rules:[
{
test:/.jsx$/,
exclude:/node_modules/,
use:[
{
loader:'babel-loader',
}
]
}
]
},
plugins:[
new ReactRefreshWebpackPlugin()
]
}
在babel.config.js中增加
module.exports = {
presets:[
[
"@babel/preset-env",
{
"useBuiltIns":"usage",
"corejs":3,
"modules":"commonjs" //使用哪种模块化,默认auto根据环境判断
}
],
"@babel/preset-react"
],
plugins:[
['react-refresh/babel']
]
}
vue下的热更新
首先要保证webpack的devServer.hot=true
vue3版本只需要下载一个vue-loader
npm i -D vue-loader
npm i -S vue3
webpack中添加如下配置
const { default : VueLoaderPlugin } = require('vue-loader/dist/plugin')
module.exports = {
devServer:{
hot:true
},
module:{
rules:[
{
test:/.vue$/,
use:[
{
loader:'vue-loader'
}
]
}
]
},
plugins:[
new VueLoaderPlugin()
]
}
webpack-dev-middleware
webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求
const express = require('express');
const webpack = require('webpack');
const webpackdm = require('webpack-dev-middleware')
const app = express()
const compile = webpack(require('./webpack.qwj.js'));
app.use(webpackdm(compile))
app.listen(3000)
webpack路径问题
output.path
output.path指定了webpack打包的总出口,是打包出来的所有文件在硬盘中的存储位置,是一个绝对路径。通常配置如下,即打包出来的所有文件都会放在项目根目录的dist文件夹下
output: {
path: path.join(__dirname, './dist'),
},
output.publicPath
output.publicPath指定了打包之后这些静态资源的根目录。比如如下配置,这时静态资源打包后上传到服务器的位置必须是在static文件夹下。
output: {
path: path.join(__dirname, './dist'),
publicPath:'', //默认值 空字符串 打包后静态资源(dist,css,img)在服务器的存放位置
publicPath:'/', //绝对路径
publicPath:'./' //相对路径
},
publicPath index.html的内部引用路径
域名+publicPath+filename
webpack-dev-server
webpack5这个功能和上面的output.publicPath合并了,并且包含contentBase,watchContentBase功能也都合并了
< webpack5 需要配置
{
devServer:{
hot:true, //开启热更新
publicPath:'/static', //这个相当于是指定index.html的存放地址,而上面的output.publicPath相当于是指定静态资源的存放地址
contentBase:path.resolve(__dirname,'public'), //当我们引入了一些不需要webpack打包配置的静态资源时,所指定的静态资源地址
watchContentBase:true, //监听上面的静态资源变化,刷新服务
}
}
解析(resolve)
webpack解析文件和加载文件的配置 webpack.js.org/configurati…
{
resolve:{
alias:{
"@":Path.resolve(__dirname,'src'),
"vue_3": Path.resolve(__dirname,'/node_modules/axios')
},
/*
alias 为某些模块import或某些模块创建别名。require例如,给一堆常用src/文件夹起别名:
"@":Path.resolve(__dirname,'src'), //这里在项目中就可以通过@来省略./src 原本'./src/**' 现在'@/**'
*/
extensions:['.ts','tsx','.js','jsx','.json'],
/*
extensions 默认 ['.js', '.json', '.wasm']
省略扩展名称,当我们访问的时候可以省略掉扩展名,尝试按顺序解决这些扩展。
如果多个文件共享相同的名称但具有不同的扩展名,webpack 将解析具有数组中第一个列出的扩展名的文件并跳过其余文件。
当我们配置后,想要访问默认配置可以'...'
['.ts', '...'],
*/
fallback:{
Path:require.resolve('path')
},
/*
fallback 正常解析失败时重定向模块请求。
Webpack 5 不再自动填充 Node.js 核心模块,这意味着如果您在浏览器或类似工具中运行的代码中使用它们,您将必须从 npm 安装兼容的模块并自己包含它们。
以下是 webpack 5 之前 webpack 使用的 polyfills 列表:
resolve: {
fallback: {
path: require.resolve('path-browserify'),
},
},
*/
mainFiles:['index','main'],
/*
mainFiles 默认值 ['index']
当我们访问一个文件路径为'./component/',这时候是在文件夹下,这时候当我们指定了字段后,它就会按照这个指定的配置查找对应名称文件
*/
modules:['node_modules'],
/*
modules 默认值 ['node_modules']
告诉 webpack 在解析模块时应该搜索哪些目录。
[path.resolve(__dirname, 'src'), 'node_modules'],
*/
}
}
mode(模式) && devtool
mode(模式)
告知 webpack 使用相应模式的内置优化。
module.exports = {
mode: 'production' //默认生产环境
};
//或者,cli中传递参数
webpack --mode=production
| 选项 | 描述 |
|---|---|
| development | 会使用DefinePlugin定义一个process.env.NODE_ENV ,值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。这时的devtool所指向的就是eval |
| production | 会使用DefinePlugin定义一个process.env.NODE_ENV ,值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin.这时的devtool所指向的就是none |
开发
module.exports = {
mode: 'development',
};
生产
module.exports = {
mode: 'production',
};
无
module.exports = {
mode: 'none',
};
如果你想根据webpack.config.js中的mode变量改变行为,你必须导出一个函数而不是一个对象:
module.exports = (env, argv) => {
if (argv.mode === 'development') {
config.devtool = 'source-map';
}
if (argv.mode === 'production') {
//...
}
return config;
};
devtool
此选项控制是否生成,以及如何生成 source map。
source-map
一种映射源码和打包后的代码的技术,一般用在开发环境调试的时候 平常当我们在开发环境进行开发时,webpack会进行打包压缩,这时候如果有代码报错 ,我们在浏览器的调试工具中看到的报错信息时卸载打包文件中的,无法看到这个文件对应的源代码文件,所以当我们开启了这个功能后,就可以在调试时看到源代码的报错位置
source-map文件中一般会有如下几个字段
//打包后的文件 main.js
最底部一般会有一个//# sourceMappingURL=[source-map文件名称]
//source-map文件map.js.map
{
version: 3//版本信息
file:'main.js' //当前source-map文件对应的文件信息
mappings://打包后文件和源代码文件的一种映射关系的算法
sources: //打包后的文件,是通过哪些源文件转换而来的
sourcesContent://打包后的源代码
names://打包后文件中会被简单字符替换的一些变量名称
sourceRoot //记录当前source-map文件的根路径
}
适用于开发环境的
eval - 每个模块都使用 eval() 执行,并且都有 //@ sourceURL。此选项会非常快地构建。主要缺点是,由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数。
eval-source-map - 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl(base64) 后添加到 eval() 中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。
cheap-eval-source-map - 类似 eval-source-map,每个模块使用 eval() 执行。这是 "cheap(低开销)" 的 source map,因为它没有生成列映射(column mapping),只是映射行数。它会忽略源自 loader 的 source map,并且仅显示转译后的代码,就像 eval devtool。这个会通过babel处理js代码
cheap-module-eval-source-map - 类似 cheap-eval-source-map,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。直接展示没有被babel处理过的js源代码
适用于生产环境的
none - 不生成 source map
source-map - 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它。
hidden-source-map - 与 source-map 相同,但不会为 bundle 添加引用注释。如果你只想 source map 映射那些源自错误报告的错误堆栈跟踪信息,但不想为浏览器开发工具暴露你的 source map,这个选项会很有用。
nosources-source-map - 创建的 source map 不包含 sourcesContent(源代码内容)。它可以用来映射客户端上的堆栈跟踪,而无须暴露所有的源代码。你可以将 source map 文件部署到 web 服务器。
特定场景
inline-source-map - source map 转换为 DataUrl(base64) 后添加到 bundle 中。
cheap-source-map - 没有列映射(column mapping)的 source map,忽略 loader source map。
inline-cheap-source-map - 类似 cheap-source-map,但是 source map 转换为 DataUrl(base64) 后添加到 bundle 中。
cheap-module-source-map - 没有列映射(column mapping)的 source map,将 loader source map 简化为每行一个映射(mapping)。
inline-cheap-module-source-map - 类似 cheap-module-source-map,但是 source mapp 转换为 DataUrl 添加到 bundle 中。
(inline-|hidden-|eval-)-(nosources-)-(cheap-(module-)-)-source-map (inline-|hidden-|eval-) 控制source map文件如何生成 inline,eval 生成base64文件,不单独产生source map文件 hidden 以单独文件存在,但是默认不加载 (cheap-(module-)-) cheap提高性能 module 对loader更加友好
-
eval- 是否使用eval执行代码模块
-
cheap- Source map是否包含行信息
-
module-是否能够得到Loader处理之前的源代码
-
inline- SourceMap 不是物理文件,而是以URL形式嵌入到代码中
-
hidden- 看不到SourceMap文件,但确实是生成了该文件
-
nosources- 没有源代码,但是有行列信息。为了在生产模式下保护源代码不被暴露
开发模式推荐使用:
eval-cheap-module-source-map,因为:- 代码每行不会太长,没有列也没问题
- 代码经过Loader转换后的差异较大
- 首次打包速度慢无所谓,重新打包相对较快
生产模式推荐使用:
none,原因- Source Map会暴露源代码
- 调试是开发阶段的事情
- 对代码实在没有信心可以使用
nosources-source-map
当我们配置mode模式为production时,devtool模式默认就是空,为development时,devtool模式默认为eval
环境配置
要区分webpack的生产和开发环境,从而使用不同的配置项
/*
webpack.common.js webpack公共项配置文件
主要作用就是抽离公共配置项,比如入口,出口,resolve,loader,plugins
module.exports = (env,argv) => {
console.log(env);//cli传递的环境参数
console.log(argv);//cli传递的所有参数
return baseOpt
}
webpack.dev.js dev环境配置
无非就是配置一下mode:'development',devtool:'选择一个需要的的source-map配置',devServe
webpack.prod.js prod环境配置
无非就是配置mode:"production",devtool:“生产环境最好不用配置”
webpack-merge合并webpack配置项插件 npm i -D webpack-merge
以上大致就是这样配置导出的时候,公共项导出的时候可以导出一个函数,
然后要注意我们通过DefinePlugin定义的环境变量只能使用在项目中,对应webpack所在的环境想要使用环境变量,可以通过我们的命令配置,来手动往process这个对象上加,最好是往process.env上添加
*/
//需要注意的点,和可以封装的路径问题
{
context:'',//默认就是执行当前文件的命令行文件所在的路径,这个和process.cwd()方法获取路径的方式保持一致,
entry:'./src/index.js', //这个没有路径问题,因为这时候的entry路径前面会拼接context上下文路径,而context上下文路径是以执行当前文件的命令行路径为基准的
}
//要在webpack中统一处理文件路径可以封装一个path.js
const Path = require('path');
const dirPath = process.cwd(); //执行这个文件的命令行文件所在路径
module.exports = (relativePath) => {
return Path.resolve(dirPath,relativePath)
}
代码拆分
因为打包会把所有的代码打包到同一个文件,所以会响应首屏加载速度,这时候我们就需要按需加载文件 webpack.js.org/guides/code…
webpack.js.org/plugins/spl… SplitChunksPlugin
入口点
module.exports={
entry:'./src/index.js',//单独口,所有依赖都会打包到同一个文件中
entry:{ //多入口
//第一种方式,不管有没有引用第三方库,还是会被打包到一个文件中
'main1':"./main",
//第二种方式,单独拿出依赖模块,如果有引入第三方库,并且想要单独打包第三方库,首先将第三方库在这里单独引入,然后在我们的业务文件中引入
'lodash_my':'lodash', //key名称是我们自定义的,value值就是依赖的包名
'main1':{
import: './src/main1.js', //入口文件地址
filename: 'main1.js', //出口文件名称和地址,会高于output.path
dependOn:'lodash_my'
},
'jquery_my':'jquery', //key名称是我们自定义的,value值就是依赖的包名
'main2':{
import: './src/main2.js', //入口文件地址
filename: 'main2.js', //出口文件名称和地址,会高于output.path
dependOn:'jquery_my'
},
'shared':['lodash','jquery'],
'main3':{
import: './src/main3.js', //入口文件地址
filename: 'main3.js', //出口文件名称和地址,会高于output.path
dependOn:'shared'
},
'main4':{
import: './src/main3.js', //入口文件地址
filename: 'main3.js', //出口文件名称和地址,会高于output.path
dependOn:['lodash','jquery']
},
//第三种方式,就是当前文件引入第三方模块,使用splitChunks拆分依赖,具体看下面的配置
'home':"./src/home.js"
},
optimization: {
runtimeChunk: 'single',//如果我们要在单个HTML页面上使用多个入口点,值设置为'single'会创建一个运行时文件,以便为所有生成的块共享。
},
}
// 如果入口块之间有任何重复的模块,它们将包含在两个包中。
// 它不够灵活,不能用于动态拆分代码与核心应用程序逻辑。
optimization
webpack.js.org/configurati… 详细配置地址
module.exports = {
output:{
chunkFilename:'', //chunk文件名
},
optimization:{
runtimeChunk: true,//当我们使用/*webpackChunkName: 'title'*/这种写法时,打包出来的文件会包含很多中间的连接层信息,当我们设置这个为true后,webpack会把这些连接层信息单独分包出来
chunkIds:'natural', //告诉 webpack 在生成chunkId时使用哪种算法
minimize:true,
minimizer:[
new TerserPlugin({
extractComments:false, //当我们去提取第三方插件的时候,webpack会把第三方插件的一些说明单独提取成一个文件,当我们不想要的时候可以配置这个
}),
],
splitChunks:{
chunks:'all', //有效值为async(只处理异步值),initial(只处理同步值),all(同步和异步都处理),
minSize:2000000, //要生成的块的最小大小(以字节为单位),当小于该值时,不会单独打包,是maxAsyncSize和maxInitialSize集成
maxSize:2000000, //当大于这个大小时,会进行拆包,而每一个拆包出来的文件大小就是minSize的大小,是maxAsyncSize和maxInitialSize集成
minChunks:3,//在拆分之前,模块必须要使用到的最小次数。
/*
这里有优先级问题,如果设置了minSize&maxSize,优先级高于minChunks
*/
cacheGroups:{ //缓存组,其实就是自定义缓存规则,是私有的规则,这里面的规则高于外部的规则
default: {
minChunks: 1,
filename: 'js/syy_[id].js',
priority: -20, //设置优先级,数值越大优先级越高
},
qwjRules:{
test:/[\/]node_modules[\/]/, //匹配node_modules中的文件
filename:'[id].bundle.js', //当且仅当它是初始块时才允许覆盖文件名。此处提供的所有占位符
priority: -10,
},
}
}
}
}
import动态导入配置,代码懒加载,预加载模块
//有些代码在首屏时可能不需要立马加载,这时候就可以用代码懒加载
()=>import('加载文件路径信息');
//需要用到某个模块时,再加载这个模块,动态导入的模块会被自动分包。通过动态导入生成的文件只是一个序号,可以使用魔法注释指定分包产生bundle的名称。相同的chunk名会被打包到一起。其实就是vue中的路由懒加载和和把某个路由下的所有组件都打包在同个异步块 (chunk) 中
import(/* webpackChunkName: 'posts' */'./post/posts').then({default: posts}) => {
mainElement.appendChild(posts())
}
/*
prefetch 与 preload 预加载模块,
prefetch:将来某些导航可能需要资源,<link rel="prefetch" href="login-modal-chunk.js">被附加到页面的头部,这将指示浏览器在空闲时间预取login-modal-chunk.js文件
preload : 在当前导航期间也需要资源
预加载的块开始与父块并行加载。预取的块在父块完成加载后开始。
预加载的块具有中等优先级并立即下载。浏览器空闲时会下载预取的块。
父块应立即请求预加载的块。未来任何时候都可以使用预取的块。
浏览器支持不同。
import(/* webpackPrefetch: true */ './path/to/LoginModal.js');
*/
引入CDN
1 自己项目打包完成后想要放到cdn
module.exports = {
output:{
publicPath:"https://www.baidu.com" //部署在服务器上的地址
}
}
//打包出来的文件
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="./icon.png">
<title>邱文军-webpack-学习</title>
<script defer="defer" src="https://www.baidu.com/js/runtime~home.f4648b45.js"></script>
<script defer="defer" src="https://www.baidu.com/js/home.c74bc0fc.js"></script>
</head>
<body><noscript><strong>We're sorry but 邱文军-webpack-学习 doesn't work properly without JavaScript enabled. Please enable
it to continue.</strong></noscript>
<div id="app"></div>
</body>
</html>
2 取消打包项目中的第三方库,直接使用cdn
module.exports = {
externals:{ //当我们有些第三方包想直接通过cdn引入,而不是项目中打包时候,就可以配置当前选项
lodash1:'_', //这里的key就是当前在项目中引用的包名称,value就是引入的第三方库的名称,这里的lodash的简写就是_
jQuery:'jQuery'
}
}
//打包出来的文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>icon.png">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
Dll库
dll动态链接库,把webpack中常用到的一些共享资源,一些较大的,不长变动的代码随着每次打包而打包! 所以dll就是相当于把这些内容单独封装出来,做成一个dll的动态链接库!
封装dll
const Path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
mode:'production',
entry:{
react:['react','react-dom'] //把这两个插件封装起来
},
output:{
filename:"dll_[name].js",
path:Path.resolve(__dirname,'dll'),
library:'dll_[name]'
},
optimization:{
minimize:true,
minimizer:[
new TerserPlugin({
extractComments:false,
})
]
},
plugins:[
new webpack.DllPlugin({
name:'dll_[name]',
path:Path.resolve(__dirname,'./dll/[name].manifest.json')
}),
]
}
使用dll
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const webpackConfig = {
entry: 'index.js',
output: {
path: 'dist',
filename: 'index_bundle.js',
},
plugins: [
new webpack.DllReferencePlugin({
context: path.resolve(__dirname),
manifest: path.resolve(__dirname,'./dll/react.manifest.json''),
}),
new HtmlWebpackPlugin(),
new AddAssetHtmlPlugin({
outputPath:'auto',
filepath: path.resolve(__dirname, './dll/dll_react.js'),
}),
],
};
TerserPlugin
const TerserPlugin = require('terser-webpack-plugin'); 这个插件使用terser来缩小/最小化你的 JavaScript。 github.com/terser/ters… //官方文档 webpack.js.org/plugins/ter… //插件文档
module.exports = {
optimization:{
// runtimeChunk:true,//当我们使用
// chunkIds:'named',
minimize:true,
minimizer:[
new TerserPlugin({
extractComments:false, //当我们去提取第三方插件的时候,webpack会把第三方插件的一些说明单独提取成一个文件,当我们不想要的时候可以配置这个
}),
],
}
}
scope hoisting(作用域提升)
过去,捆绑时 webpack 的权衡之一是捆绑中的每个模块都将包装在单独的函数闭包中。这些包装器函数使 JavaScript 在浏览器中的执行速度变慢。相比之下,像 Closure Compiler 和 RollupJS 这样的工具“提升”或将所有模块的范围连接到一个闭包中,并允许您的代码在浏览器中具有更快的执行时间。
这个插件将在 webpack 中启用相同的连接行为。默认情况下,此插件已在生产mode中启用,否则禁用。如果您需要覆盖生产mode优化,请将optimization.concatenateModules选项设置为false. 要在其他模式下启用串联行为,您可以ModuleConcatenationPlugin手动添加或使用以下optimization.concatenateModules选项:
module.exports = {
plugins:[
new webpack.optimize.ModuleConcatenationPlugin();//以后想要用到,最后到时写esModule模式的代码
],
//或者
optimization: {
usedExports: true,
minimize: true,
concatenateModules: true
}
}
Tree shaking(树摇)
优化代码中没有用到的代码,并在打包时去除! 这个功能对esModule模块化支持最好,common.js模块化支持较弱
usedExports
标记出源代码中没有用到的功能和代码
module.exports = {
optimization:{
usedExports:true, //标记
minimize:true,
minimizer:[
new TerserPlugin({}),//移除
]
}
}
sideEffects
某些模块下面有些函数是不被使用的,可以通过配置usedExports为true对那些不常用的代码进行标记,然后通过TerserPlugin插件将其排除,但是webpack中还有一些副作用代码,上面的方式无法做到,这时候我们就可以通过sideEffect来做到,具体配置如下
//在webpack.config.js中开启这个功能:
module.exports={
optimization: {
usedExports: true,
minimize: true,
concatenateModules: true,
sideEffects: true
}
}
//在package.json里面增加一个属性sideEffects,值为false,表示没有副作用,没有用到的代码则不进行打包。确保你的代码真的没有副作用,否则在webpack打包时就会误删掉有副作用的代码,比如说在原型上添加方法,则是副作用代码;还有CSS代码也属于副作用代码。
{
"name": "awesome npm module",
"version": "1.0.0",
"sideEffects": false, //设置为false时,对所有文件起作用
"sideEffects": [
"./src/js/*.js", //文件路径,可以使用glob匹配模式
], //内部设置的所有文件将会被忽略优化
}
//上述的配置都是全局的,如果说有些类型的文件不需要优化,也可以单独配置
module.exports = {
module:{
rules:[
{
test:/.css$/,
sideEffects:true, //局部配置优化
use:[
env ? MiniCssExtractPlugin.loader : "style-loader",
{
loader:"css-loader",
options:{
esModule:false,
importLoaders:1
}
}
]
}
]
}
}
Css Tree shaking
用于删除未使用的 css 的Webpack插件。
npm i purgecss-webpack-plugin -D
const path = require('path')
const glob = require('glob')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
module.exports = {
plugins:[
new PurgeCSSPlugin({
paths: glob.sync(`${Path('src')}/**/*`, { nodir: true }), //要处理的文件路径
safelist:['body','html','qiuwenjun'] //要保留的一些css选择器名称,白名单
}),
]
}
CSS抽离和压缩
// MiniCssExtractPlugin可以提取CSS到单个文件 当css代码超过150kb左右才建议使用。
// OptimizeCssAssetsWebpackPlugin 压缩输出的CSS文件
// npm install --save-dev mini-css-extract-plugin
// npm install --save-dev optimize-css-assets-webpack-plugin
const MiniCssExtracPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
optimization:{
minimize:true,
minimizer:[
new TerseWebpackPlugin(), // 指定了minimizer说明要自定义压缩器,所以要把JS的压缩器指指明,否则无法压缩
new CssMinimizerPlugin() //为什么要配置在这里,因为如果配置在plugins选项当中,那这个插件在任何情况下都会使用,而配置到这里,则只会在开启时才会使用,webpack建议我们压缩类的插件配置在这里
]
},
module:{
rules:[
{
test:/.css$/,
use:[
env[环境参数] ? MiniCssExtractPlugin.loader : "style-loader",
{
loader:"css-loader",
options:{
esModule:false,
importLoaders:1
}
},
'postcss-loader',
]
},
]
},
plugins:[
new MiniCssExtractPlugin({
filename:"css/[name].[contenthash:8].css"
})
]
}
打包压缩,服务端压缩
开发环境中我们可以通过配置
module.exports = {
devServer:{
compress:true,//开启开发环境服务端gizp压缩
}
}
如果说我们要在生产环境也有这个功能,那这时候我们可以通过webpack打包,自动生成压缩文件 www.webpackjs.com/plugins/com…
npm i -D compression-webpack-plugin
const CompressionPlugin = require("compression-webpack-plugin")
module.exports = {
plugins: [
new CompressionPlugin({
test:/.(css|js)$/, //处理所有匹配此正则的资源
threshold://只处理比这个值大的资源。按字节计算
minRatio://只有压缩率比这个值小的资源才会被处理
})
]
}
webpack打包库文件
封装库文件
const Path = require('path');
module.exports = {
entry:"./src/index.js",
mode:'development',
devtool:false,
output:{
filename:"[name].[hash:8].js",
path:Path.resolve(__dirname,'dist'),
library:"Last", //库名称,也就是当前模块的名称
libraryTarget:'umd', //打包出来的文件使用什么模块化规范
globalObject:"this" //内部self指向谁,this 浏览器下指向window,node下指向global
}
}
打包时间和内容分析
npm install --save-dev speed-measure-webpack-plugin
//这个插件可以让我们知道所配置的webpack代码哪里有优化的需要,优化你的 webpack 构建速度的第一步是知道你应该把注意力集中在哪里
npm install --save-dev webpack-bundle-analyzer
//这个插件可以分析我们打包出来的内容
输出文件名hash
生产模式下,文件名使用Hash
-
项目级别的hash
output: { filename: '[name]-[hash].bundle.js' }, -
chunk级别的hash,相关联的文件
output: { filename: '[name]-[chunkhash].bundle.js' }, -
文件级别的hash:8是指定hash长度 (推荐)
output: { filename: '[name]-[contenthash:8].bundle.js' },
esLint(语法校验)
查看esLint文档 ../lint/esLint