文章目录
传统模块化开发和问题
项目中有很多模块,一个模块就是一个js文件,项目中加载这些js的时候发送很多http请求,影响性能。
<!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">
<title>我的网页</title>
<!-- <style>
body {
background-color: grey;
}
</style> -->
<!-- rel="stylesheet"必须写 -->
<link href="./test.css" rel="stylesheet">
</head>
<body>
<h1>我的网页</h1>
<div id="app">
<!-- <div>header</div>
<div>content</div>
<div>footer</div> -->
</div>
<!-- <script src="./test.js"></script> -->
<script src="./header.js"></script>
<script src="./content.js"></script>
<script src="./footer.js"></script>
<script src="./test.js"></script>
<!-- 传统模块化开发 -->
<!-- 多加载了js文件,发送了多次http请求 -->
<!-- 变量的来源不是很确定 -->
<!-- 加载顺序问题 -->
<!-- 现在的模块化开发使用ES6的import export 使用模块 -->
<!-- 但浏览器不能识别import export -->
<!-- => webpack 解决以上问题 -->
</body>
</html>
body {
background-color: rgb(212, 207, 207);
}
// alert('hello')
const app = document.getElementById('app')
const header = document.createElement('div')
header.innerText='header';
app.appendChild(header)
const content = document.createElement('div')
content.innerText='content';
app.appendChild(content)
const footer = document.createElement('div')
footer.innerText='footer';
app.appendChild(footer)
/***
* 以上就是面向过程编程,所有的代码都写在一个js文件里面,
* 对以后项目的维护,功能的扩展都不是很友好
*
* => 面向对象编程
*
*/
new Header();
new Content();
new Footer();
// header.js
function Header(){
const header = document.createElement('div')
header.innerText='header';
app.appendChild(header)
}
// content.js
function Content(){
const content = document.createElement('div')
content.innerText='content';
app.appendChild(content)
}
// footer.js
function Footer(){
const footer = document.createElement('div')
footer.innerText='footer';
app.appendChild(footer)
}
webpack输入输出
https://webpack.docschina.org/
- 本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。
- webpack刚开始默认只支持js文件的引入
- 现在对于css,vue格式也可以引入,但需要做一些loader配置
入口(entry)->npx webpack
-
npx webpack index.js
告诉webpack模块打包的入口文件是index.js,然后开始打包 -
配置入口文件
webpack.config.js
(默认配置名)
module.exports = {
entry: './index.js',
};
可以在控制台直接执行npx webpack
- 自定义配置文件
webpack.dev.config.js
可以在控制台直接执行npx webpack --config ./webpack.dev.config.js
输出(output)
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。
你可以通过在配置中指定一个 output 字段,来配置这些处理过程:
const path = require('path');
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
//在任何模块文件内部,可以使用__dirname变量获取当前模块文件所在目录的完整绝对路径。
filename: 'my-first-webpack.bundle.js',
publicPath: 'http://localhost:8080/' //打入引入js文件的域名
},
};
- 为什么默认是main.js
const path = require('path');
module.exports = {
entry: {
main:'./index.js',
// entry中key值为打包后的名字
// 可以有多个入口
},
};
多个入口的配置
const path = require('path');
module.exports = {
entry: {
main: './index.js',
// entry中key值为打包后的名字
// 可以有多个入口
sub:'./index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
// 输入两个文件main sub
},
},
;
浏览器缓存
-
Performance
https://webpack.docschina.org/configuration/performance/#root -
关闭性能优化的提示
module.exports = {
//...
performance:false,
//关闭性能优化的相关内容,
// 发现npx weback 打包的一些报警信息没有了
};
-
浏览器缓存
每次打包生产相同的bundle.js文件,浏览如果开启了缓存就会从缓存中读取,不会更新最新的文件 -
output filename使用由生成的内容产生的 hash:
webpack.config.js
module.exports = {
//...
output: {
filename: '[name][contenthash:8].bundle.js',
},
};
模式(mode)
直接使用mode
通过选择 development, production 或 none 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production。
module.exports = {
mode: 'development',
};
- development 模式下打包生成的bundle.js不会压缩
使用对应mode的配置文件
同过不同的文件配置
- webpack.deb.js
- webpack.prod.js
- packahe.json
{ "scripts":{ "build":"webpack --config ./build/webpack.prod.js", "dev":"webpack-dev-server --config ./build/webpack.dev.js", "dev:build":"webpack --config ./build/webpack.dev.js", } }
webpack merge
- 安装webpack merge
- webpack.base.js 公共配置部分放在这儿
const path = require('path');
module.exports = {
entry: {
main: './index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
},
;
- 分别在开发和生产模式的文件中合并配置
const {merge} = require('webpack-merge')
// 公共配置
const baseConfig = require('./webpack.base')
// 开发配置
const devConfig = module.exports = {
"mode":"development",
"devtool":"",
"devServer":{},
"plugins":[]
}
module.exports = merge(baseConfig,devConfig)
loader基本使用
- webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。
- loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块
属性
在 webpack 的配置中,loader 有两个属性:
- test 属性,识别出哪些文件会被转换。
- use 属性,定义出在进行转换时,应该使用哪个 loader。
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js',
},
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
};
以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:test 和 use。这告诉 webpack 编译器(compiler) 如下信息:
“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 ‘.txt’ 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。”
https://webpack.docschina.org/loaders/在这里安装loader
处理图片的loader:file-loader,url-loader
- 处理图片格式的配置
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[hash].[ext]',
// 生成的dist中图片名是原图片名(默认文件名是一个md5生成的)
// name配置项是配置打包生成的文件的名字,使用的是placeholder语法
// [name]表示的是原文件的名字;
// [hash] 表示的是这次打包的hash值
// [ext]表示的是原文件的后缀;
outputPath: 'images',
// 生成的图片在dist/images/路径下
},
}
],
},
],
},
};
- 使用
import testJpg from './resource/test.jpg';
console.log('name',testJpg)
const img = new Image();
img.src = testJpg;
const app = document.getElementsByTagName('div')[0]
app.appendChild(img);
执行npx打包命令后,打开dist中的首页可以看到图片显示
- url-loader返回base64
https://v4.webpack.docschina.org/loaders/url-loader/
A loader for webpack which transforms files into base64 URIs.
- url-loader works like file-loader, but can return a DataURL if the file is smaller than a byte limit.
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
// 防止图片过大,限制大小
}
}
]
}
]
}
}
- 综合使用url-loader的limit
-
图片很大->url loader->base64字符串很大->bundle.js体积会很大->index.html加载bundle.js时间就很长
-
图片很小->url loader->图片base64直接设置在img的src属性上->不需要发送额外的http请求图片
-
图片很小->file-loader->单独生成图片文件->index引入->bunde.js很小->页面加载块->多发送一次http请求
-
图片很大->file-loader->bunde.js很小->图片单独请求
module.exports = { module: { rules: [ { test: /\.(png|jpg|gif)$/i, use: [ { loader: 'url-loader', options: { name: 'dirname/[hash].[ext]', // 生成的dist中图片名是原图片名(默认文件名是一个md生成的) outputPath: 'images', // 生成的图片在dist/images/路径下 limit: 8192 // 防止图片过大,限制大小 } } ] } ] } }
-
样式loader:style-loader,css-loader
- 可以通过在js通过操作loader来处理简单的样式
- 如果引入css文件,需要安装对应的loader
- style-loader
- css-loader
- style-loader 与 css-loader 结合使用,且顺序不能更改
- css-loader 的作用是把 css文件进行转码 ,而 style-loader 的作用是把转码后的css文件插入到相应的文件中去
- 打包生成的样式会把他插入到index.html中的style标签内
webpack.config.js
{
module: {
rules: [
{
test: /\.css$/,
// use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
uer: ['style-loader','css-loader']
},
];
}
}
loader执行顺序
从右往左 从下往上
sass-loader
// webpack.config.js
module.exports = {
...
module: {
rules: [{
test: /\.scss$/,
use: [
"style-loader", // 将 JS 字符串生成为 style 节点
"css-loader", // 将 CSS 转化成 CommonJS 模块
"sass-loader" // 将 Sass 编译成 CSS,默认使用 Node Sass
]
}]
}
};
- importLoaders
选项允许你配置在 css-loader 之前有多少 loader 应用于@imported 资源。
// webpack.config.js
{
test: /\.scss/,
use: [
{ loader: 'style-loader'},
{ loader: 'css-loader',
options: {
importLoaders: 2 //如果css中import scss文件 应用2个层级的loader
},
},
{ loader: 'postcss-loader' },
{ loader: 'sass-loader'}
]
}
postcss-loader+autoprefix配置样式前缀
https://v4.webpack.docschina.org/loaders/postcss-loader/#sourcemap
- webpack.config.js设置loader
{
test: /\.css/,
use: [
{ loader: 'style-loader', options: { sourceMap: true } },
{ loader: 'css-loader', options: { sourceMap: true } },
{ loader: 'postcss-loader', options: { sourceMap: true } },
{ loader: 'sass-loader', options: { sourceMap: true } }
]
}
- postcss-loader的插件autoprefix可以给css样式加上厂商前缀(也要设置目标浏览器范围)
https://v4.webpack.docschina.org/loaders/postcss-loader/#autoprefixing
https://stackoverflow.com/questions/49782806/webpack-4-postcss-loader-and-autoprefixer-plugin
- webpack.config.js加载autoprefixer
{ test: /\.css$/, use: [ { loader: 'postcss-loader', options: { plugins: () => [require('autoprefixer')({ 'browsers': ['> 1%', 'last 2 versions'] })], } }, ] }
or
- postcss.config.js加载autoprefixer
module.exports = ({ file, options, env }) => ({ // parser: file.extname === '.sss' ? 'sugarss' : false, plugins: { // require('autoprefixer')({...options}), // require('autoprefixer')({ // // 也可以在这设置目标浏览器 // browsers : ['last 100 versions'] // }) require('autoprefixer')({ 'browsers': ['> 1%', 'last 2 versions'] }) } })
- 设置目标浏览器范围
https://github.com/browserslist/browserslist
All tools will find target browsers automatically, when you add the following to- package.json:
"browserslist":[ "> %1", "last 2 versions" ]
- Or in .browserslistrc config:
- package.json:
CSS模块化
在讲如何实现CSS模块化之前先讲讲要利用模块化来解决什么问题:
- 样式私有化
- 避免被其他样式文件污染
- 可复用
https://github.com/css-modules/css-modules
https://webpack.docschina.org/loaders/css-loader/#modules
{
test: /\.css$/i,
loader: "css-loader",
options: {
modules: true,
},
}
- 模块化原理:给当前css的选择器加上一个后缀
/* components/Button.css */
.normal { /* normal 相关的所有样式 */ }
.disabled { /* disabled 相关的所有样式 */ }
/* components/Button.js */
import styles from './Button.css';
console.log(styles);
buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`
生成的 HTML 是
<button class="button--normal-abc53"> Processing... </button>
打印结果
Object {
normal: 'button--normal-abc546',
disabled: 'button--disabled-def884',
}
CSS Modules 对 CSS 中的 class 名都做了处理,使用对象来保存原 class 和混淆后 class 的对应关系。
plugins插件
https://v4.webpack.docschina.org/plugins/
https://webpack.js.org/awesome-webpack/
html-webpack-plugin
https://v4.webpack.docschina.org/plugins/html-webpack-plugin/
var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
// 插件都是实例化使用
plugins: [new HtmlWebpackPlugin()]
};
这将会产生一个包含以下内容的文件 dist/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>webpack App</title>
</head>
<body>
<script src="index_bundle.js"></script>
</body>
</html>
- 之前我们自己手动创建dist/index.html,然后引入打包后的dist/bundle.js
- 使用html-webpack-plugin插件,webpack打包后创建index.html.并自动引入bundle.js
- 但webpack创建的这个htm文件,仅仅是引入了bundle.js,再没有其他内容
- 所以我们要对插件配置其他内容
var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin({
template:'./src/index.html'
//配置dist/html模板
})]
};
webpack常用插件
- CleanWebpackPlugin
用于在打包前清理上一次项目生成的 bundle文件。默认情况下,此插件将删除webpack的Output.Path目录中的所有文件
devtool->sourceMap
https://v4.webpack.docschina.org/configuration/devtool/#devtool
- devtool -> parame:string false
- 选择一种 source map 格式来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。
webpack.config.js
const path = require('path');
module.exports = {
mode:'development',
devtool:'none',
entry: {
main: './index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
},
}
index.js
console.logg('hello')
// 这行代码webpack打包的时候不会报错,打开网页后浏览器报错,问题定位到dist/main.js
- devtool:‘none’,当网页报错的时候浏览器会把错误定位到dist/main.js文件中,打包速度最快
- devtool:‘eval-source-map’,浏览器会把错误定位到原始源代码文件中
- devtool:‘source-map’,原始源代码,生成源代码和打包后代码关系的map文件
- 最佳实践
- 开发环境
cheap-module-eval-source-mapeval-cheap-module-source-map - 生产环境 cheap-module-source-map
- 开发环境
- cheap:提示问题在哪一行但不提示列
- module:cheap不管第三方模块和loader,加上module则会提示
- eval:不会生成源代码和打包后代码的映射文件,而是放在main.js中
webpack-dev-server
- 每次修改了源代码,都要重新运行
npx webpack
ornpm run build
进行构建打包,刷新页面才能看到修改的页面 - webpack-dev-server可以优化这个过程
- webpack-dev-server服务器使用node+express编写的
https://v4.webpack.js.org/configuration/dev-server/
- install
npm install webpack-dev-server --save-dev
- config webpack.config.js
var path = require('path'); module.exports = { //... devServer: { // contentBase: path.join(__dirname, 'dist'), // webpack5中contentBase已经失效了 static: { directory: path.join(__dirname, 'dist'), }, compress: true,//压缩 port: 9000, open: true //告诉 dev-server 在 server 启动后打开浏览器。默认禁用。 // proxy 可以处理跨域问题 }, };
- config package.json
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", "dev" : "webpack-dev-server" },
npm run dev
devServer.proxy
- 如果你有单独的后端开发服务器 API,并且希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用。
- 通常通过请求转发来解决跨域问题
- 只适用于开发环境
在 localhost:3000 上有后端服务的话,你可以这样启用代理:
webpack.config.js
module.exports = {
//...
devServer: {
// 1.
// proxy: {
// '/api': 'http://localhost:3000'
// },
// 2.
// proxy: {
// '/api': {
// target: 'http://localhost:3000'
// }
// }
proxy: {
'/api': {
// 一旦devServer(5000)服务器接收到 /api/xxx 的请求,就会把请求转发到另一个服务器(3000)
// 浏览器和服务器之间有跨域,但是服务器和服务器之间没有跨域
target: 'http://thirdserver.com:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
// 所有以/api开头的请求,把/api转换为空字符串
// 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api
},
secure: false,
//默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。
// 如果你想要接受,修改配置如下:
}
}
}
};
请求到 /api/users 现在会被代理到请求 http://localhost:3000/api/users。
webpack VS webpack-dev-server 构建区别
"scripts":{
"build":"webpack", // 打包后文件在dist/main.js可以找到
"dev":"webpack-dev-server" //打包后文件在内存中
}
HotModuleReplacementPlugin热更新插件
- 认识HMR热更新
- input输入框输入文字,修改源码后,网页自动刷新然后重新请求,状态丢失,
- 修改源码网页自动刷新属于热部署不属于热更新
- 解决:HotModuleReplacementPlugin插件
https://webpack.docschina.org/plugins/hot-module-replacement-plugin/
https://webpack.js.org/guides/hot-module-replacement/#enabling-hmr
:::warning
HMR 绝对不能被用在生产环境。
:::
const webpack = require('webpack');
// ........
module.exports = {
plugins:[new webpack.HotModuleReplacementPlugin()]
// webpack内置插件
}
https://stackoverflow.com/questions/69102254/webpack-options-has-an-unknown-property-hotonly-invalid-options-object-dev-s
The latest versions automatically apply HotModuleReplacementPlugin plugin when you set hot: true, so please check you don’t have HotModuleReplacementPlugin in your plugins if you have hot: true/hot: “only”. You will get a warning as " [webpack-dev-server] “hot: true” automatically applies HMR plugin, you don’t have to add it manually to your webpack configuration." if you have the preceding settings.
- style-loader 自动配置了热更新,更改了css,样式自己就改变了,react、vue框架自身也配置了,如果需要自定义
if(module.hot){ //在你要监听的文件里面写
console.log('hot')
// './print.js'
// Ignored an update to unaccepted module ./src/print.js -> ./src/index.js
// accept第一个参数必须是当前依赖的模块路径
module.hot.accept('./print.js',()=>{ // ./src值得是更新文件路径
console.log('代码热更新,写自己的代码控制')
const oldSpan = document.getElementsByTagName('span')[0];
oldSpan.innerText=`HRP-sp${print.a}`;
})
}
babel->ES6
https://www.babeljs.cn/docs/index.html
- Babel 是一个 JavaScript 编译器
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情:
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过引入第三方 polyfill 模块,例如 core-js)
- 源码转换(codemods)
- preset-env 使用
https://www.babeljs.cn/setup#installation
https://webpack.js.org/loaders/babel-loader/
https://babel.docschina.org/docs/en/next/configuration/
-
npm install -D babel-loader @babel/core @babel/preset-env
-
config
module: { rules: [ { test: /\.m?js$/, exclude: /(node_modules|bower_components)/,//排除第三方代码 use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] }
-
babel.config.json
https://babel.docschina.org/docs/en/next/usage/
Tree Shaking
- Tree Shaking 只支持ES Moudle,不支持CommonJs
- ES Moudle->静态引入,编译时引入 import export
- CommonJS->动态引入,执行时引入 require module.exports
const flag = true;
if(flag){
const {add} = require('./math')
}
if(flag){
import {add} from ('./math')
// 这种动态引入会报错
// import and export may only appear at the top level
}
Tree Shaking指的就是当我引入一个模块的时候,我不引入这个模块的所有代码,我只引入我需要的代码,这就需要借助webpack里面自带的Tree Shaking这个功能,帮助我们实现。Tree Shaking只支持ES Module(import…) 不支持require…
-
https://developer.mozilla.org/zh-CN/docs/Glossary/Tree_shaking
-
https://webpack.js.org/guides/tree-shaking/
-
当在development模式下配置tree shaking时:
mode:'development', optimization: { usedExports: true, },
- 开发模式中为了调试方便未使用的代码并没有被删除,只是注释了未被引用的方法
-
在production 模式下不用在webpack.config.js中配置.usedExports默认自动开启
-
如果Tree Shaking不生效请检查source-map
side-effect-free(无副作用)
- 除了返回值以外,影响到函数外部的任何东西(显示器、网络、文件、全局变量等)的效果都叫side effect
通过 package.json 的 “sideEffects” 属性,来实现这种方式。
{
"name": "your-project",
"sideEffects": false
}
如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false,来告知 webpack 它可以安全地删除未用到的 export。
因为Tree Shaking会删除掉我们未使用的export函数,因为polyfill都在window上定义的,没有export,所以会被删除掉.
配置side effect排除副作用的检查
:::tip
“side effect(副作用)” 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
:::
{
"name": "your-project",
"sideEffects": ["@babeli/polyfill","*.css"]
}
- webpack5 这个配置项已经去掉了
configuration has an unknown property ‘sideEffects’. These properties are valid:
需要在babel-loader rules中配置
代码分割
https://webpack.docschina.org/guides/code-splitting/
- 代码一般分为业务逻辑代码和其他工具包代码,全部打包到main.js中,文件很大
- 如果修改了业务逻辑代码重新打包的时候也会打包其他工具代码,浏览器会重新下载这个很大的main.js
- src/index.js
import _ form 'lodash' console.log('hello world!');//每次修改业务逻辑,webpack打包的时候也会将lodash一块重新打包到bundle.js const result = _.join(['test1','test2','test3'],'-'); console.log('result',result)
- 解决->代码分隔->配置多个入口
手动解决->配置多个入口和出口
- 入口起点:使用 entry 配置手动地分离代码。
- src/index,js
console.log('hello world!'); const result = _.join(['test1','test2','test3'],'-'); console.log('result',result)
- src/lodash.js
import _ form 'lodash' window._= _
- webpack.config.js
module.exports = { entry:{ main:'./src/index.js', lodash:'./src/lodash.js' //打包生成 main.js和lodash.js并引入到index.html中 }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js', } }, }
SplitChunksPlugin 插件解决
-
SplitChunksPlugin 去重和分离 chunk。
https://webpack.docschina.org/plugins/split-chunks-plugin/ -
src/index.js
import _ form 'lodash'
console.log('hello world!');//每次修改业务逻辑,webpack打包的时候也会将lodash一块重新打包到bundle.js
const result = _.join(['test1','test2','test3'],'-');
console.log('result',result)
- webpack配置SplitChunks
module.exports = {
//...
optimization: {
splitChunks: {
chunks:'all'
},
};
import懒加载->代码分隔
https://webpack.docschina.org/guides/lazy-loading/#root
// Note that because a network request is involved, some indication
// of loading would need to be shown in a production-level site/app.
button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
const print = module.default;
// 注意当调用 ES6 模块的 import() 方法(引入模块)时,必须指向模块的 .default 值,因为它才是 promise 被处理后返回的实际的 module 对象。
print();
});
- import懒加载代码分隔也是通过SplitChunksPlugin配置的.
splitChunks.chunks:This indicates which chunks will be selected for optimization. When a string is provided, valid values are all, async, and initial. Providing all can be particularly powerful, because it means that chunks can be shared even between async and non-async chunks.
moudle chunks bundle 的理解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtOQyPHA-1654528404640)(https://gitee.com/jingyu7/pic/raw/master/202201061256177.png)]
- module,chunk 和 bundle 其实就是同一份逻辑代码在不同转换场景下的取了三个名字:
- 我们直接写出来的是 module,webpack 处理时是 chunk,最后生成浏览器可以直接运行的 bundle。
SplitChunksPlugin的常见配置
- splitChunks.chunks
This configuration object represents the default behavior of the SplitChunksPlugin.
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',//默认只有异步代码进行代码分隔 all, async, and initial
minSize: 20000, //如果引入的包>20000KB就进行代码分隔
minRemainingSize: 0,//通过确保拆分后剩余的最小 chunk 体积
minChunks: 1, //拆分前必须共享模块的最小 chunks 数。
// 这个配置表示split前单个非按需导入的module的并行数的最低下限
// 注:只对import或require直接引入的module有效。
// 1. minChunks设置为n
// 2. 假设有m个入口点,这m个入口点都直接引入了某个模块module(通过import或require直接或间接地引入了模块),也就是共享次数为m
// 3. 当m至少等于n时,module才会被单独拆分成一个bundle
maxAsyncRequests: 30,//按需加载时的最大并行请求数。
maxInitialRequests: 30,//入口点的最大并行请求数
enforceSizeThreshold: 50000,//强制执行拆分的体积阈值和其他限制
cacheGroups: { //缓存组
// 对于缓存组是一个对象,处了可以有上面的chunks、minSize、minChunks、maxAsyncRequests、maxInitialRequests、name外,还有其他的一些参数:
// 如果不在缓存组中重新赋值,缓存组默认继承上面的选项,但是还有一些参数是必须在缓存组进行配置的
// 缓存组的概念理解
// 源代码中引入了lodash jquery 都分别分隔到了对应的js
// 假如希望它两合并后分隔
// 当分析到引入lodash时缓存到defaultVendors,分析到jquery满足规则也缓存起来
// 当所有模块到分析结束后,再把defaultVendors组一起分隔
defaultVendors: {
test: /[\\/]node_modules[\\/]/, //捕获模块规则
priority: -10, //优先级
reuseExistingChunk: true,
//如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
//假如源代码中不同文件中都引入lodash模块,
//在第一次引入分析后被当前缓存组缓存,后面的此模块的引入分析复用之前缓存结果
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
CSS代码分割MiniCssExtractPlugin
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
- 之前css样式文件被styleLoader处理打包后放进了main.js->css in js->最后放在head的style中
- 一般是应用于生产环境
- mini-css-extract-plugin
https://webpack.docschina.org/plugins/mini-css-extract-plugin/
webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
};
垫片Shimming 预置依赖ProvidePlugin
Automatically load modules instead of having to import or require them everywhere.
- 可以用于全局配置插件,也可以用于老代码中jquery的兼容
- 语法
new webpack.ProvidePlugin({
identifier: 'module1',
// ...
});
- Usage: jQuery
To automatically load jquery we can point both variables it exposes to the corresponding node module:
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
// webpack为我们提供了ProvidePlugin的插件,上面配置的意思是,当我们在模块中遇到‘$’就会去自动的引入jquery
});
],
};
Then in any of our source code:
// in a module
$('#item'); // <= works
jQuery('#item'); // <= also works
// $ is automatically set to the exports of module "jquery"
版本4和版本5的区别
性能优化
有两种优化方向
- 打包构建的速度
- 产出代码体积
babel-loader-exclude,cacheDirectory
- 主要是提高js打包构建的速度,第一个是缓存第二个是exclude
- css loader没有构建缓存
-
exclude,include
exclude:path.resolve(__dirname,'node_modules')
-
问题
- 在webpack中配置babel-loader时,为什么要排除node_modules这个文件夹呢?
- 这个文件夹里js模块万一也有类似箭头函数的ES6语法呢?
-
因为如果你不排除这个文件夹的话,你在自己的代码中引用的其他工具库的代码,也会被babel读取并重新编译,JavaScript代码的编译比较慢,很多情况下,你自己的业务代码只占了10%,但引用的其他工具库的代码,编译完以后可能占到80~90%,假如编译你自己的业务代码需要10秒钟,那整个项目的代码,就需要100秒,你会发现速度非常慢,不利于提高开发效率。另一方面,现在发布到npm上面的工具库,一般都包含了源代码和编译以后的代码,同时默认引用的是编译以后的代码,这样相当于你把编译完以后的代码又编译了一遍,实际上并没有任何效果,反而浪费了时间。所以我们在配置的时候,一般都将node_modules当中的工具库代码,排除在babel的编译范围之外。
-
虽然 npm 中的 package 没有明确规范需要编译成 es5/es3的语法,但是目前事实就是绝大部分公开的 package 都是编译到这个规范的,所以已经是一个事实标准了。
-
如果node_modules里的某些包有es6语法怎么办?
答:通过正则指定node_modules中的某些包不排除
-
-
babel 缓存:在babel-loader中的options中开启 cacheDirectory:true
把已经编译好的兼容性代码就放在缓存中,下次编译的时候如果缓存中有就直接使用编译后的代码,
只对js生效module:{ rules:[ test: /\,js$/, options:{ cacheDirectiry:true }, inclued:srcPath, exclude:path.resolve(__dirname,'node_modules') ] }
webpack.IgnorePlugin对moment的处理
webpack 中的 IgnorePlugin 在打包时忽略本地化内容,如引入了一个插件,只用到了中文语言包,打包的时候把非中文语言包排除掉
在项目中经常用到moment库处理时间,有没有发现引入moment之后,项目build完的文件大小明显大了不少,下面就来分析一下原因, 以及如何做优化。
//
import moment from 'moment'
// 引入中文
import 'moment/locale/zh-cn'
// 设置中文
moment.locale('zh-cn');
let momentStr = moment().subtract(10, 'days').calendar();
console.log('现在时间:', momentStr);
网上搜了一波才知道, 打包时把所有的locale都打包进去了, 初步解决方案是用webpack.IgnorePlugin来处理。
IgnorePlugin又是做了什么操作?
- IgnorePlugin 原理分析
IgnorePlugin的原理是会移除moment的所有本地文件,因为我们很多时候在开发中根本不会使用到。 这个插件的使用方式如下:
...
plugins: [
new webpack.IgnorePlugin(/^./locale$/, /moment$/) // 配置忽略规则
// 忽略moment库中的locale文件夹啊
]
那么你可能会有疑问,所有本地文件都被移除了,但我想要用其中的一个怎么办。不用担心,你依然可以在代码中这样使用:
const moment = require('moment');
require('moment/locale/ja');//如果需要请单独引用
moment.locale('ja');
https://github.com/zhanzizhen/how-to-optimize-momentjs-with-webpack/blob/master/README.md
noParse配置
一般使用.min.js结尾的文件,都是已经经过模块化处理的,那么这个时候就没必要在进行loder或者webpack分析了,noParer的字面意思也是不再解析。
module: {
noParse: /jquery/, // 不解析模块中的依赖关系 提高打包效率
rules: []
}
-
ignorePlugin和noParse的对比
- IgnorePlugin 直接就将符合匹配条件的模块,不再进行引入,代码中没有。
- noParse 该引入还是会引入,只是不参与loader或webpack的解析及打包。
-
noParse不会改变打包体积,只会加快构建速度
happPack多进程打包
由于 JavaScript 是单线程模型,要想发挥多核 CPU 的能力,只能通过多进程去实现,而无法通过多线程实现。
由于运行在 Node.js 之上的 Webpack 是单线程模型的,所以 Webpack 需要处理的任务要一个一个进行操作。而 Happypack 的作用就是将文件解析任务分解成多个子进程并发执行。子进程处理完任务后再将结果发送给主进程。所以可以大大提升 Webpack 的项目构件速度
watch 和 watchOptions
Webpack 可以监听文件变化,当它们修改后会重新编译。
启用 Watch 模式。这意味着在初始构建之后,webpack 将继续监听任何已解析文件的更改。
module.exports = {
//...
watch: true,
};
webpack-dev-server 和 webpack-dev-middleware 里 Watch 模式默认开启。
- watchOptions一组用来定制 watch 模式的选项:
module.exports = {
//...
watchOptions: {
ignored: /node_modules/,
//监听大量文件会导致大量的 CPU 或内存占用。可以使用正则排除像 node_modules 如此庞大的文件夹:
aggregateTimeout: 200,//批量更新
// 当第一个文件更改,会在重新构建前增加延迟。这个选项允许 webpack 将这段时间内进行的任何其他更改都聚合到一次重新构建里。以毫秒为单位:
},
};
DllPlugin
DllPlugin,DllReferencePlugin就是事先把常用但又构建时间长的代码提前打包好(例如 react、react-dom),取个名字叫 dll。后面再打包的时候就跳过原来的未打包代码,直接用 dll。这样一来,构建时间就会缩短,提高 webpack 打包速度。
-
就是个缓存吗!拿空间换时间。
-
autodll-webpack-plugin 也是类式插件, 之前被 vue-cli 使用
-
webpack 4 有着比 dll 更好的打包性能。所以已经不使用autodll-webpack-plugin了
当我后续找到 autodll-webpack-plugin,并发现 dll 已经被抛弃时,其实还是有些失望,觉得自己的之前的努力都白费了,不由自主产生 学不动 的想法。但是当我仔细想了一下 dll 的原理,发现也就是那么一会事儿,拿空间换时间,只不过配置复杂了一些。
所以这也提醒我们,学习新知识的时候,不要专注于流程的配置和调参。因为流程终会简化,参数(API)终会升级。要抓大放小,把精力放在最核心的内容上,因为核心的思想是最不容易过时的。