一、模块化开发 Commonjs 规范
Commonjs 是以同步模式加载模块
一个文件就是一个模块
每个模块都有单独的作用域
通过module.exports
导出成员
通过require
函数载入模块
ES Module ESM 是 ES6 引入的 JS 标准模块化规范,它的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系和输入和输出的变量。
功能 模块功能主要由两个命令构成:export
和import
。 export 命令用于规定模块的对外接口, import 命令用于输入其他模块提供的功能。
特性 通过给 script 添加 type = module
的属性,就可以以 ES Module 的标准执行其中的 js 代码了
自动采用严格模式,忽略‘use strict’
每个 ES Module 模块都是单独的私有作用域
ES Module 是通过 CORS 去请求外部 js 模块的,服务器要支持 CORS
ES Module 的 script 标签会延迟执行脚本(页面渲染完在执行脚本)
export 导出
导出
导出成员使用 export 修饰成员声明的方式,如:
1 2 export var name = 'yan; export var age = 18'
1 2 3 var name = "yan" ;var age = 18 ;export { name, age };
使用 as 给导出的成员重命名,导入时就要使用重命名后的变量名,如:
1 2 var name = "yan" ;export { name as firstName };
如果重命名为 default 那这个成员就会做为当前模块默认导出的成员
在导入这个成员就必须要给这个成员重命名,default 是一个关键词不能作为一个变量使用
导入
导入使用 import 导入,如:import { name } from './module.js'
,name 为导出的变量名
导入默认导出传成员可以不使用花括号,变量名可以根据需要随便取,如:import name from './module.js'
如果导入的变量名为 default,需要使用 as 重命名,如:import { default as firstName } from './module.js'
导入导出的注意事项
export { name, age }
该写法不是对象字面量的,如果要对象字面量的写法应该是:export default { name, age }
,这样花括号会被理解为对象
import {} from './module.js'
,花括号不是解构
导出成员时,导出的是这个成员的引用,导出的变量是只读的(在导入的文件中无法改变变量的值),如,在导出成员的文件中在一秒后改变变量的值,在导入变量的文件中两秒后输出变量值,输出的结果是改变后的值
1 2 3 4 5 6 7 8 9 10 export var a = 1 ;setTimeout (() => { a = 2 ; }, 1000 ); import { a } from "./module.js" ;setTimeout (() => { console .log (a); }, 2000 );
import 导入用法
import 必须使用完整的路径不能省略扩展名,即使是引入 index 也要完整的路径不能省略 index
import 可以使用完整的 url 加载模块 import { name } from 'https://xxx.com/xxx.js'
相对路径,在网页引用资源是可以省略‘./’的,但是 import 不可以省略,如果省略了就是以字母开头 ES Module 会以为在加载第三方模块,可以使用绝对路径和完整的 url
如果想要执行某个模块而不用提取模块成员,花括号为空即可,这是就只会执行这个模块,不会提出成员,如:import {} from './module.js'
,简写:import './module.js'
如果模块导出的成员特别多,而导入的都用到,可以使用星号‘_’,把这个模块的成员全部导入,在通过 as 将导入的成员全部放到一个对象中,导出的成员都会作为这个对象的属性,如:import _ as mod from './module.js'
动态导入模块,不可以通过判断使用if (true){import {name} from './module.js'}
,如果需要动态导入,可以使用 import 函数import('./module.js')
,import 函数返回的是一个 promise
如果模块同时导出默认成员和命名成员,在导入时可以给默认成员重命名import { name, default as age } from './module.js'
,也可以import age(默认成员), { name } from './module.js'
导出导入成员
import 可以配合 export 使用,效果就是将导入的结果直接作为当前模块的导出成员,那么在当前作用域就不能访问这些成员,如:export { name, age } from './module'
,利用这种特性可以把散落的模块组织在一起,如,一个 index 把零散的模块组织一起,然后再导出
1 2 3 4 5 6 7 8 9 10 var Button = "Button Component" ;export default Button ;export var Avatar = "Avatar Component" ;export { default as Button } from "./button.js" ;export { Avatar } from "./avatar.js" ;import { Button , Avatar } from "./index.mjs" ;
ES Modules 浏览器环境 Polyfill
在不支持 ES Module 的浏览器中,可以使用 browser-es-module-loader 第三方库,在执行的时候解析
但是在支持 ES Module 的浏览器中,这样会执行两次,可以添加 script 标签的新属性 nomodule
nomodule:在不支持 ES Module 的浏览器工作
1 2 3 4 5 6 7 8 9 10 11 12 <script nomodule src ="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js" > </script > <script nomodule src ="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js" > </script > <script nomodule src ="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js" > </script >
ES Modules in Node.js - 支持情况
在 node 中使用 ES Module 文件后缀名为‘mjs’
执行 node --experimental-modules 文件
,experimental-modules:启用 ES Module 实验特性
可以通过 import 导入原生的模块和第三方模块import fs from 'fs'; import _ form 'loadsh'
import { camelCase } from 'lodash'
,不支持,因为第三方模块都是导出默认成员;内置模块兼容了 ES Module 的提取成员方式,所以import { writeFileSync } from 'fs'
支持
ES Modules in Node.js - 与 CommonJS 交互
ES Module 中可以导入 Commonjs 模块
Commonjs 中不能导入 ES Module 模块
Commonjs 始终只会导出一个默认成员
注意 import 不是解构导出对象
ES Modules in Node.js - 与 CommonJS 的差异
ES Module 中没有 Commonjs 中的那些模块全局成员(require/module/exports/**filename/**dirname)
Commonjs 中 require/module/exports 在 ES Module 中使用 import 和 export 替代,**filename 和 **dirname 可以通过import.meta.url
可以拿到当前工作文件的文件 url 地址,可以使用原生的 url 模块的fileURLToPath()
方法可以把文件 url 转换为路径
1 2 3 4 import { fileURLToPath } from "url" ;import { dirname } from "path" ;const __filename = fileURLToPath (import .meta .url );const __dirname = dirname (__filename);
ES Modules in Node.js - 新版本进一步支持
在 package.json 文件添加 type 字段值为 module,此时文件扩展名可以用 js,而不需要 mjs
此时想要使用 Commonjs 规范,那么文件后缀名要改为 cjs
ES Modules in Node.js - Babel 兼容方案
安装 babel 相关依赖 yarn add @babel/node @babel/core @babel/preset-env
babel 工作依赖插件,preset 是一个插件集合
执行 yarn babel-node index.js(想要转换的文件) --presets=@babel/preset-env
或者添加一个.babelrc
文件
babel 的核心就是 preset-env,用于将 ES6 转换成 ES5 代码。
1 2 3 { "presets" : [ "@babel/preset-env" ] }
二、Webpack 打包 模块打包工具的由来
ES Module 存在环境兼容问题
模块文件过多,网络请求频繁
所有的前端资源都需要模块化
Webpack 配置文件
webpack 默认以src/index.js
为入口文件
在项目的根目录添加webpack.config.js
文件,可以自定义相应的配置,该文件是运行在 nodejs 环境中
该文件导出一个对象,通过导出对象的属性就可以完成相应的配置选项
entry 属性:webpack 打包入口文件的路径,如果是相对路径‘./’
不能省略
output 属性:webpack 打包输出的位置,值要求是一个对象,通过对象的 filename 属性指定输出的文件名,path 属性指定输出的目录(绝对路径)
Webpack 工作模式
在 webpack.config.js 文件对象中添加 mode 属性
mode 默认值是 production,mode 可选值有 production、development、none
production:自动启动优化,会把代码压缩加密
development:会自动优化打包速度,添加一些调式过程中的辅助
none:运行最原始的打包,不做额外的处理
loader webpack 的核心就是 loader。
Webpack 资源模块加载
webpack 内容默认只会处理 js 文件
处理其他类型的文件要安装使用对角的 loader(加载器)
loader 的输入和输出都是字符串.
使用 loader,在webpack.config.js
导出的对象中的 module 属性,添加一个 rules 数组
rules 数组就是针对加载其他资源模块的规则配置
每个规则对象要设置两个属性,
test:正则表达式,匹配打包过程中遇到的文件;
use:匹配到的文件使用的 loader,use 的值可以是单个 loader 字符串也可以是一个数组 loader,如果是一个数组,会从右往左执行
css-loader 的作用就是将 css 文件转换为一个 js 模块;具体实现就是将 css 代码 push 到一个数组中
style-loader 的作用就是将 css-loader 转换后的结果通过 style 标签追加到页面上
loader 是 webpack 的核心特性
借助不同的 loader 就可以加载任何类型的资源
file-loader 文件资源加载器
安装依赖 yarn add file-loader
在配置文件中添加 rules { test: /.png$/, use: 'file-loader' }
webpack 默认所有打包的结果放在网站根目录;为项目中的所有资源指定一个基础路径,在 output 中添加 publicPath 属性,默认值为空表示网站的根目录
把 publicPath 值修改为 ‘dist/‘ 注意“/”不能省略,打包后是一个变量名(publicPath 值)拼接资源名(资源名前面没有“/”)所有“/”不能省略
publicPath
是整个项目的静态资源的基础路径,一般在生产模式下使用.
而webpack-dev-server
中的publicPath
是打包后代码存放的路径.这里的文件访问的静态资源路径还是output
中的publicPath
.所以二者要保持一致.
假设 output.publicPath
设置成了 './dist/'
,那么打包后的 JS 引用路径为 ./dist/main.js
。这里会存在一个问题,相对路径在本地能正常访问到。但是如果把静态资源托管到 CDN 上,访问路径显然不能使用相对路径的。如果设置成 '/dist/'
,则打包后的访问路径是 localhost:8080/dist/main.js
,此时本地无法访问。 一般解决方法就是利用 webpack.DefinePlugin
来定义一个全局变量(process.env.NODE_ENV)区分开发、生产环境来设定不同的值,或者是采用两份不同的配置文件来进行打包。 一般来说,output.publicPath 应该以 ‘/‘ 结尾,而其他 loader 或插件的配置不要以 ‘/‘ 开头。
url-loader URL 加载器 优点: 不用再发起 http 请求
Data URLS 格式:data(协议):[mediatype(媒体类型和编码)][;base64],
安装插件 yarn add url-loader --dev
在配置文件中添加 rules
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module .exports = { module : { use : [ { test : /.png$/ , use : { loader : "url-loader" , option : { limit : 10 * 1024 , }, }, }, ], }, };
loader 中做大小限制还是需要安装 file-loader,不然无法解析.
最佳实践
小文件使用 Data URLs,减少请求次数
大文件单独提取存放,提高加载速度
Webpack 常用加载器 loader 分类
编译转换类:会把加载的资源转为 js 代码,如css-loader
文件操作类:会把加载的资源拷贝到输出的目录,导出文件访问路径,如file-loader
代码检查类:目的是统一代码风格,如eslint-loader
Webpack 与 ES6 wepack 并不能转换 es6 中的其他特性,只能处理模块相关的import,export
安装babel-loader @babel/core @babel/preset-env
在配置文件中 module rules 添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module .exports = { module : { rules : [ { test : /.js$/ , use : { loader : "babel-loader" , options : { presets : ["@babel/preset-env" ], }, }, }, ], }, };
Webpack 加载资源的方式
遵循 ES Modules 标准的 import 声明
遵循 Commonjs 标准的 require 函数
遵循 AMD 标准的 define 函数和 require 函数
Loader 加载器的非 JavaScript 也会触发资源加载
样式代码中的@import 指令和 url 函数
HTML 代码中图片标签的 src 属性
如果用require
导致 esm 模块代码,需要用导入值的default
属性导入.不过,除非必要,不要混用.
1 const heading = require ("./heading.js" ).default ;
Webpack 核心工作原理
webpack 会根据配置找到打包入口文件,然后顺着入口文件的代码
根据代码中的 import 或者 require 解析推断这个文件依赖的资源模块
然后分别解析每个资源模块对应的依赖,形成整个项目中所有用到文件之间的依赖关系的依赖树
webpack 递归依赖树,找到每个节点对于的资源文件
根据配置文件中的 rules 属性找到模块对应的加载器,找到对于的资源放在打包结果中,实现整个项目的打包
Webpack 开发一个 Loader loader 都需要导出一个函数,这个函数就是对加载资源的处理.
loader 负责资源文件从输入到输出的转换
对于同一个资源可以依次使用多个 loader (css-loader -> style-loader)
loader 模块导出一个函数;该函数输入的是加载到的资源文件内容;函数体是处理内容的过程;输出的是经过处理过的资源文件内容结果,结果必须是 javascript 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import about from "./about.md" ;console .log (about);const marked = require ("marked" );module .exports = (source ) => { const html = marked.parse (source); return html; }; module .exports = { mode : "none" , entry : "./src/main.js" , output : { filename : "bundle.js" , path : path.join (__dirname, "dist" ), publicPath : "dist/" , }, module : { rules : [ { test : /.md$/ , use : ["html-loader" , "./markdown-loader" ], }, ], }, };
plugin loader 实现资源加载,plugin 解决除了资源加载,其他的自动化工作. 比如,打包前清除 dist 目录(为了清除上一次代码),文件压缩,拷贝静态文件到输出目录. 也就实现了大部分的前端工程化的工作.
plugin 插件机制介绍
增强 webpack 自动化能力
loader 专注实现资源模块加载
plugin 解决其他自动化工作
清除 dist 目录
拷贝静态文件至输出目录
压缩输出代码
plugin 之 自动清除输出目录插件
安装插件 yarn add clean-webpack-plugin --dev
配置文件中导入插件
在配置对象中 plugins 属性,该属性是专门配置插件的地方,属性值是一个数组
数组里就是导入的类型创建的实例.
1 2 3 4 const { CleanWebpackPlugin } = require ("clean-webpack-plugin" );module .exports = { plugins : [new CleanWebpackPlugin ()], };
plugin 之 自动生成 HTML 插件
安装插件 yarn add html-webpack-plugin --dev
在配置文件中导入插件
在配置对象中 plugins 属性,该属性是专门配置插件的地方,属性值是一个数组
同时输出多个页面文件,可以再通过 HtmlWebpackPlugin 生成多个实例,通过 filename 指定文件名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { plugins : [ new HtmlWebpackPlugin ({ title : "Webpack Plugin Sample" , meta : { viewport : "width=device-width" , }, template : "./src/index.html" , }), new HtmlWebpackPlugin ({ filename : "about.html" , }), ], };
plugin 之 复制静态文件
安装插件 yarn add copy-webpack-plugin --dev
有些文件不需要打包,比如网站图标 icon,可以使用copy-webpack-plugin
直接复制.
建议上线前再使用此插件,否则频繁打包会影响性能,平时开发可以使用的devserver中的contentBase属性设置静态资源路径
1 2 3 new CopyWebpackPlugin ([ 'public' ])
plugin 之 开发一个插件 原理: 钩子机制,通过在生命周期的钩子中挂载函数实现的.
一般就是定义一个类,在类中定义 apply 方法,使用的时候就是通过这个类去构建实例.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class MyPlugin { apply (compiler ) { console .log ("my plugin 启动" ); compiler.hooks .emit .tap ("MyPlugin" , (compilation ) => { for (const name in compilation.assets ) { if (name.endsWith (".js" )) { const contents = compilation.assets [name].source (); const withoutComments = contents.replace (/\/\*\*+\*\//g , "" ); compilation.assets [name] = { source : () => withoutComments, size : () => withoutComments.length , }; } } }); } }
webpack-dev-server 集成了自动编译
,自动刷新浏览器
功能
1 2 3 4 5 # 安装 pnpm add webpack-dev-server -D # 运行 pnpm webpack-dev-server # 如果加--open会自动打开浏览器
Webpack Dev Server 静态资源访问
Dev Server 默认只会 serve 打包输出文件
只要是 webpack 打包输出的文件都可以直接被访问
1 2 3 4 devServer : { contentBase : "./public" ; }
Webpack Dev Server 代理 API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 module .exports = { devServer : { proxy : { "/api" : { target : "https://api.github.com" , pathRewrite : { "^/api" : "" , }, changeOrigin : true , }, }, }, };
Source Map Source Map 介绍
调试和报错都是基于运行代码
source map 映射转换后代码和源代码关系,通过 source map 逆向解析
解决源代码与运行代码不一致所产生的调试问题
Webpack 配置 Source Map 在配置项添加 devtool: 'source-map'
Webpack eval 模式的 Source Map
eval 是否使用 eval 执行模块代码,带 eval,用 eval 执行模块代码,只能定位文件,不能定位具体位置.
eval-source-map 可以定位具体位置
cheap-eval-source-map 可以定位行,不能定位列.
cheap-source map 是否包含行信息
module 是否能够得到 Loader 处理之前的源代码
带 module 的模式下解析的出来的原代码是没有经过 loader 加工,手写的源代码
不带 module 是 loader 加工过后的代码
inline-source-map:source map 是因 dataURL 嵌入代码,其他的 source map 是以物理文件存在
hidden-source-map:开发工具看不到 source map 效果 在开发第三方包比较有用
Webpack 选择 Source Map 模式
开发模式
cheap-module-eval-source-map
:代码每行不会超过 80 个字符;代码进过 loader 转换后差异较大需要 module 模式(loader 处理前的源代码);首次打包速度慢无所谓,重写打包相对较快
生成模式
none
或nosources-source-map
:Source Map 会暴露源代码
热更新 HMR Webpack HMR 体验(热更新)
应用运行过程中实时替换某个模块,应用运行状态不受影响
HMR 集成在webpack-dev-server
中
开启 HMR
css 样式(经过 loader 处理的)可以热更新但是 js 还是会刷新浏览器,webpack 中的 HMR 不可以开箱即用
webpack 中的 HMR 需要手动处理模块热替换逻辑
1 2 3 4 5 6 7 8 const webpack = require ("webpack" );module .exports = { target : "web" , devServer : { hot : true , }, plugins : [new webpack.HotModuleReplacementPlugin ()], };
Webpack 使用 HMR API Webpack 处理 JS 模块热替换 js 热更新:保存旧数据,代码发生变化后把数据回填
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let lastEditor = editor;module .hot .accept ("./editor" , () => { const value = lastEditor.innerHTML ; document .removeChild (editor); const newEditor = createEditor (); newEditor.innerHTML = value; document .body .appendChild (newEditor); lastEditor = newEditor; }); module .hot .accept ("./better.png" , () => { img.src = background; console .log (background); });
HMR 注意事项
如果没有开启热更新,但是 js 代码做了热更新处理,先做判断 if (module.hot){// 热更新逻辑}
hot 会刷新浏览器,报错信息会被刷走;hotOnly
不会刷新浏览器,可以把错误信息输出
打包后会自动去除 js 热更新逻辑代码
Webpack 生产环境优化
mode:none、production、development
为不同的工作环境创建不同的配置
Webpack 不同环境下的配置
1 2 3 4 5 6 7 8 9 10 module .exports = (env, argv ) => { const config = { }; if (env === "production" ) { } };
一个环境对应一个配置文件
webpack.common.js 存放公共配置
webpack.dev.js 开发配置,把公共配置和开发配置合并(使用 loadsh merge)
webpack.prod.js 生产环境,把公共配置和生产配置合并(使用 loadsh merge)
使用webpack-merge
插件替换 loadsh 工具.
在没有默认配置文件的情况就要执行 pnpm webpack --config webpack.dev.js
或者在 scripts 中添加命令 "build": "webpack --config webpack.dev.js"
1 2 3 4 5 const common = require ('./webpack.common' )const merge = require ('webpack-merge' )module .exports = merge (common, { ... }
Webpack DefinePlugin
DefinePlugin 为代码注入全局成员
接收的是一个对象,每一个键值都会注入到代码中
文件代码中直接使用键,打包后把注入的值 直接替换到代码中,definePlugin 传递的字符串应该是代码
1 2 3 4 5 6 7 8 9 10 11 12 const webpack = require ("webpack" );module .exports = { plugins : [ new webpack.DefinePlugin ({ API_BASE_URL : '"https://api.github.com"' , }), ], }; console .log (API_BASE_URL );
Tree Shaking tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)
在生成模式下自动开启,对于冗余、未引用代码不会打包
不是指某个配置选项,是一组功能搭配使用后的优化效果
只支持 ESM,不支持 CommonJS.(ESM 是静态引入,cms 是动态引入)
1 2 3 4 5 6 7 8 9 10 11 12 13 module .exports = { mode : "none" , entry : "./src/index.js" , output : { filename : "bundle.js" , }, optimization : { usedExports : true , minimize : true , }, };
Webpack 合并模块—concatenateModules
尽可能的将所有模块合并输出到一个函数中
提升运行效率,减少代码体积
1 2 3 4 5 6 7 8 9 10 11 module .exports = { mode : "none" , entry : "./src/index.js" , output : { filename : "bundle.js" , }, optimization : { concatenateModules : true , }, };
Webpack Tree Shaking 与 Babel
babel 和 tree shaking 同时使用会导致 tree shaking 失效
tree shaking 前提是 ES Modules,由 webpack 打包的代码必须使用 ESM
babel-loader 转换代码时可能会把 ES Modules 转为 CommonJS 新版的 babel-loader 不会导致 tree shaking 失效,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 module .exports = { module : { rules : [ { test : /\.js$/ , use : { loader : "babel-loader" , options : { presets : [ ["@babel/preset-env" , { modules : "commonjs" }], ], }, }, }, ], }, };
sideEffects
允许通过配置方式标识代码是否有副作用(模块执行时除了导出成员之外所做的事情)
一般用于开发 npm 包时 标记是否有副作用
生产模式自动开启 会检查当前代码所属的 package.json 有没有 sideEffects 标识,以此来判断这个模块是否有副作用
如果这个模块没有副作用,那些没有用到的模块不会被打包
package.json 添加 sideEffects: false
表示 package.json 所影响的项目当中所有的代码都没有副作用
1 2 3 optimization : { sideEffects : true ; }
Webpack sideEffects 注意
使用 sideEffects 前提确保代码真的没有副作用,否则 webpack 再打报时会误删有副作用的代码
如在 Number 原型添加方法然后载入、载入 css 也是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Number .prototype .pad = function (size ) { let result = this + '' while (result.length < size) { result = '0' + result } return result } import './extend.js' console .log (8. pad (3 ))
解决:在 package.json 关闭副作用(设置为 false)或者标识哪些文件有副作用
1 2 3 4 5 "sideEffects" : [ './src/extends.js' , '*.css' ]
代码分割
所有代码最终都被打包到一起,打包文件体积过大
分包,按需加载
多入口打包
动态导入
Webpack 多入口打包
多页应用程序,一个页面对应一个打包入口,公共部分单独提取
多个入口可以把 entry 定义为对象,一个属性就是一个入口,键就是入口名,值就是入口对应的文件路径
多入口,输出也要多个,可以给 filename 的值添加 [name]
占位 outpur:{filename: '[name].bundle.js'}
这样 [name]
就会被替换为入口名称
plugins 中的 HtmlWebpackPlugin 插件会自动输入注入所有打包结果的 html,所以此时入口文件会注入所有的打包结果
可以给 HtmlWebpackPlugin 插件配置注入的打包文件 new HtmlWebpackPlugin({chunks:['index']}), new HtmlWebpackPlugin({chunks:['album']})
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 const { CleanWebpackPlugin } = require ("clean-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { mode : "none" , entry : { index : "./src/index.js" , album : "./src/album.js" , }, output : { filename : "[name].bundle.js" , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, ], }, plugins : [ new CleanWebpackPlugin (), new HtmlWebpackPlugin ({ title : "Multi Entry" , template : "./src/index.html" , filename : "index.html" , chunks : ["index" ], }), new HtmlWebpackPlugin ({ title : "Multi Entry" , template : "./src/album.html" , filename : "album.html" , chunks : ["album" ], }), ], };
Webpack 提取公共模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 module .exports = { optimization : { splitChunks : { chunks : "all" , minSize : 20000 , minChunks : 1 , cacheGroups : { defaultVendors : { test : /[\\/]node_modules[\\/]/ , name : "vendors" , priority : -10 , }, default : { minChunks : 2 , name : "default" , priority : -20 , }, }, }, }, };
Webpack 动态导入
需要用到某个模块时,再加载这个模块
动态导入的模块会被自动分包
一般我们都是在文件头直接 import 文件的,这会造成资源浪费
可以使用 ES Modules 的 import()在需要导入的地方导入模块,该方法返回的是 promise 可以拿到导出的对象
1 2 3 4 5 if (true ) { import ("./posts" ).then (({ default : posts } ) => { mainElement.append (posts ()); }); }
Webpack 魔法注释
在 import()方法中添加特定的注释 import(/* webpackChunkName: 'posts' */ './posts')
这样在打包后文件名就会带上这个 webpackChunkName 值
如果 webpackChunkName 相同,打包后会打包在一起
安装插件 yarn add mini-css-extract-plugin --dev
注意: 如果 css 小于 150k,就不要用这个了,不然单独生成一个文件,就多次请求,效果可能不如style-loader
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const MiniCssExtractPlugin = require ("mini-css-extract-plugin" );module .exports = { module : { rules : [ { test : /\.css$/ , use : [ MiniCssExtractPlugin .loader , "css-loader" , ], }, ], }, plugins : [new MiniCssExtractPlugin ()], };
Webpack OptimizeCssAssetsWebpackPlugin— 压缩 css
安装插件 yarn add optimize-css-assets-webpack-plugin --dev
如果配置在 plugins 中这个插件在任何情况都会正常工作
配置在 minimizer 中 只有 minimizer 开启(生产环境自动开启)是才工作
webpack 建议压缩创建配置在 minimizer 中,以便通过 minimizer 选项统一控制
当 minimizer 使用一个数组,webpack 认为我们要自定义所使用的压缩器插件,webpack 内部的 js 压缩器就会被覆盖,如果不配置 js 压缩器,这是打包,js 代码不会被压缩
手动把 js 压缩器添加回来,安装插件 yarn add terser-webpack-plugin --dev
1 2 3 4 5 6 7 8 9 10 11 const OptimizeCssAssetsWebpackPlugin = require ("optimize-css-assets-webpack-plugin" );const TerserWebpackPlugin = require ("terser-webpack-plugin" );module .exports = { optimization : { minimizer : [ new TerserWebpackPlugin (), new OptimizeCssAssetsWebpackPlugin (), ], }, };
Webpack 输出文件名 Hash
在部署时服务器开启静态文件缓存,如果缓存时间过长,程序发生更改,得不到更新
生产环境下,文件名使用 Hash,文件名发生变化 对客户端而言全新的文件名就是全新的请求,就不会有缓存问题
[hash]【占位符】:整个项目级别的,项目中有任何地方改动,这次打包的 hash 值都会发生变化
[chunkhash]【占位符】:chuankhash 级别,打包过程中只要是同一路的打包 chunkhash 都是相同的
[contenthask]【占位符】:文件级别 hash,根据文件输出内容生成 hash 值(不同的文件就有不同的 hash 值)
指定 hash 值长度[chunkhash:8]【占位符】
1 2 3 4 5 module .exports = { output : { filename : "[name]-[contenthash:8].bundle.js" , }, };
Polyfill 可翻译为垫片,
引入方法
入口文件引入
1 2 3 4 5 # npm安装 pnpm add core-js regenerator-runtime # 在入口文件顶部导入 import 'core-js/stable' import 'regenerator-runtime/runtime'
webpack 引入
1 2 3 moduls.exports = { entry : ["core-js/stable" , "./src/index.js" ], };
预设配置 target 1 2 3 4 5 6 7 8 9 10 11 12 13 module .exports = { presets : [ [ "@babel/preset-env" , { target : { chrome : 30 , }, }, ], ], };
配合browserslistrc
这个文件是目标环境配置表,也就是说通过配置,可以让我们的代码运行在哪些指定的浏览器或 node 环境,比如下边这种配置的意思就是,市场份额大于 1%的浏览器,并且不考虑 ie10 以下版本的浏览器,并且不是已’死亡’(指 24 个月没有官方的更新)的浏览器.
1 2 3 4 > 1 % not ie <= 10 not dead
上面预设配置第一个参数 targets 里边就可以填写.browserslistrc 中的内容,效果是一样的,
useBuiltIns useBuiltIns
有三个值.默认是false
.其他是entry
和usage
.entry
: 需要在入口或者 webpack 配置文件中引入core-js
. Babel 会根据我们.browserslistrc
文件中的环境配置(这里是 chrome 60 版本),来补齐 chrome 60 版本所有不支持的新增 ES6API,引入 chrome60 版本所有的 polyfill,不管你的入口文件中有没有用到相应的 API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 npm install core-js -S npm install regenerator-runtime -S (根据@babel/core版本判断是否需要安装) module .exports = { presets : [["@babel/preset-env" , { useBuiltIns : 'entry' , corejs : 3 }]], plugins : [] } import 'core-js/stable' let promise = Promise .resolve ('success' )chrome 60
usage
: 就把上面 entry 改了就行,作用就是类似于按需加载,只把用的 Promsie 进行 polyfill 补齐.
corejs 就是 corejs 的版本,推荐 3.
modules 可以配置成”amd” | “umd” | “systemjs” | “commonjs” | “cjs” | “auto” | false,默认为 auto,这个参数的含义是,是否将 ES6 模块化语法转译成其他的语法,设置为 false 的话,转译的结果就还是 ES6 模块化语法,就是 import,export default 这种,不会使用 require(CommonJs 语法)这种进行导入导出,如果使用 webpack 或 rollup 等打包工具,推荐将 modules 设置为 false,这样的话就有利于打包工具进行静态分析,从而做 tree shaking 等优化操作
范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 module .exports = { presets : [ [ "@babel/preset-env" , { useBuiltIns : "usage" , corejs : { version : "^3.25.2" , proposals : true , }, modules : false , }, ], ], plugins : ["@babel/plugin-transform-runtime" ], };
Postcss 用来处理 css 样式兼容性的工具,用于适配不同的浏览器.
1 pnpm add postcss-cli postcss-loader autoprefixer
autoprefixer 自动适配浏览器,在browserslistrc
文件中写上要适配的浏览器种类
1 2 3 4 5 6 7 >0.01 % last 2 versions not dead
可以将配置写在单独的postcss.config.js
中,或者 webpack 配置中
1 2 3 4 module .exports = { plugins : [require ("autoprefixer" )], };
postcss-preset-env 在一些陈旧的项目中可能会看到 autoprefixer 这个插件,现在我们可以直接使用 postcss-preset-env 这个插件,自动添加前缀的功能已经包含在了其中
在项目根目录下新增 postcss.config.js 配置文件:
1 2 3 module .exports = { plugins : ["postcss-preset-env" ], };
postcss-loader 在 webpack 中的配置
1 2 3 4 5 6 7 8 9 10 11 moudule.exports = { modules : { rules : [ { test : /.css$/ , use : ["style-loader" , "css-loader" , "postcss-loader" ], }, ], }, };
静态资源 在 webpack5 之前,加载图片、字体等资源需要我们使用 url-loader、 file-loader 等来处理 从 webpack5 开始,我们可以使用内置的资源模块类型 Asset Modules type,来替代这些 loader 的工作 资源模块类型 Asset Modules type 分为四种:
asset/resource 发送一个单独的文件并导出 URL,之前通过使用 file-loader 实现
asset/inline 导出一个资源的 data URI,之前通过使用 url-loader 实现
asset/source 导出资源的源代码,之前通过使用 raw-loader 实现
asset 在导出一个 data URI 和发送一个单独的文件之间自动选择,之前通过使用 url-loader 实现,并且可以配置资源体积限制
1 2 3 4 5 6 7 8 9 10 11 12 13 module .epxorts = { module : { rules : [ { test : /\.(png|svg|jpe?g|gif)$/ , type : "asset" , generator : { filename : "images/[name]-[hash][ext]" , }, }, ], }, };
文件缓存 webpack5 之前我们常用 cache-loader 、hard-source-webpack-plugin 做文件缓存,加速二次构建, webpack5 现在内置了这种能力,默认会把编译的结果缓存到内存中,通过配置还可以缓存到文件系统中 修改 webpack.base.js:
1 2 3 4 5 6 7 module .exports = { cache : { type : "filesystem" , }, };
多个 webpack 配置文件 可以根据开发环境配置多个 webpack 配置文件,将共配置抽离出来。 一般配置如下: 基础:webpack.base.config.js 开发:webpack.dev.config.js 生产:webpack.prod.config.js
webpack-merge 使用该插件进行合并操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const { merge } = require ("webpack-merge" );const baseConfig = require ("./webapck.base" );const devConfig = { mode : "development" , devtool : "cheap-module-eval-source-map" , devServer : {}, }; module .exports = merge (baseConfig, devConfig);