内容概要
- 1、模块化演变过程
- 2、模块化规范
- 3、常用的模块化打包工具
- 4、基于模块化工具构建现代Web应用
- 5、打包工具的优化技巧
1、模块化演变过程
文件划分方式
- 污染全局作用域
- 命名冲突问题
- 无法管理模块依赖关系 原始方式完全依靠约定
命名空间方式
IIFE
模块化规范 + 模块加载器
CommonJS
- 一个文件就是一个模块
- 每个模块都有单独的作用域
- 通过 module.exports 导出成员
- 通过 require 函数载入模块
CommonJS 是以同步模式加载模块
AMD(Asynchronous Module Definition)
- AMD 使用起来相对复杂
- 模块 JS 文件请求频繁
require.js
// 因为 jQuery 中定义的是一个名为 jquery 的 AMD 模块
// 所以使用时必须通过 'jquery' 这个名称获取这个模块
// 但是 jQuery.js 并不在同级目录下,所以需要指定路径
//定义一个模块
define('module1', ['jquery', './module2'], function ($, module2) {
return {
start: function () {
$('body').animate({ margin: '200px' })
module2()
}
}
})
//载入一个模块
require(['./modules/module1'], function (module1) {
module1.start()
})
Sea.js + CMD
// 兼容 CMD 规范(类似 CommonJS 规范)
define(function (require, exports, module) {
// 通过 require 引入依赖
var $ = require('jquery')
// 通过 exports 或者 module.exports 对外暴露成员
module.exports = function () {
console.log('module 2~')
$('body').append('<p>module2</p>')
}
})
参考网址
AMD 和 CMD 的区别有哪些? SeaJS 和 RequireJS 的差异 模块化开发之AMD规范 模块化开发之CMD规范 模块化开发之CommonJS规范 ES6中Module语法与加载实现
2、模块化规范
- CommonJS in node.js
- ES Modules in Browers
ES Modules 特性
- serve .
特性清单
- 自动采用严格模式,忽略‘use strict’
- 每个ESM模块都是单独的私有作用域
- ESM 是通过 CORS 去请求外部 JS 模块的
- ESM 的 script 标签会延迟执行脚本 defer
ES Modules 导入和导出
- browser-sync . --file **/*.js
导出里面可以给变量重命名用 as ,对应导入也要用新的重命名的名字。 注意,default是关键字。
ES Modules 导入和导出 注意事项
{ name, hello } 不是一个对象字面量
export default { name, age } 这个是字面量对象
导出成员的引用,内存空间引用,引用关系给到外部
// 它只是语法上的规则而已
export { name, age }
ES Modules 导入和导出 导入import
1、路径:./相对路径;/绝对路径; 完整的url;
2、加载模块并不提取它,不需要外界控制的子功能模块
// import {} from './module.js'
// import './module.js'
3、提取所有成员
// import * as mod from './module.js'
// console.log(mod)
4、动态导入模块
// import('./module.js').then(function (module) {
// console.log(module)
// })
5、命名成员和默认成员
// import { name, age, default as title } from './module.js'
import abc, { name, age } from './module.js' //逗号左边默认成员
console.log(name, age, abc)
ES Modules 导入和导出 直接导出所导入的成员
在组件文件夹下面创建index.js用来放全部的导出文件,注意default
ES Modules in Browser Polyfill兼容方案
Browser ES Module Loader node地址获取:unpkg.com/XXX 2个
IE : Promise Polyfill 1个
<script nomodule></script> nomodule,在不支持的浏览器上工作
只适合开发阶段,不适合生产阶段
ES Modules in Node.js
1、创建.mjs文件 cd XX 进入相应的目录 2、- node --experimental-modules index.mjs //实验特性
原生模块
第三方模块
- yarn add lodash
直接提取模块内的成员,内置模块兼容了 ESM 的提取成员方式
ES Modules in Node.js 与 CommonJS 模块交互
- ES Module 中可以导入 CommonJS 模块
- CommonJS 中不能导入 ES Module 模块
- CommonJS 始终只会导出一个默认成员
- 注意 import 不是解构导出对象
ES Modules in Node.js 与 CommonJS 模块的差异
nodemon --experimental-modules esm.mjs
// require, module, exports 自然是通过 import 和 export 代替
// __filename 和 __dirname 通过 import 对象的 meta 属性获取
// const currentUrl = import.meta.url
// console.log(currentUrl)
// 通过 url 模块的 fileURLToPath 方法转换为路径
ES Modules in Node.js 新版本进一步支持 ESM
// Node v12 之后的版本,可以通过 package.json 中添加 type 字段为 module,
// 将默认模块系统修改为 ES Module
// 此时就不需要修改文件扩展名为 .mjs 了
// 如果需要在 type=module 的情况下继续使用 CommonJS,
// 需要将文件扩展名修改为 .cjs
ES Modules in Node.js Babel 兼容方案
- yarn add @babel/node @babel/core @babel/preset-env --dev
- yarn babel-node
- yarn babel-node index.js --presets=@babel/preset-env
- yarn remove @babel/preset-env
- yarn add @babel/plugin-transform-modules-common.js --dev
- yarn babel-node index.js
preset 就是一组插件
3、常用的模块化打包工具
ES Modules 存在环境兼容问题
模块文件过多,网络请求频繁
所有的前端资源都需要模块化
代码编译:开发阶段的 es6 编译 生产阶段的 es5
模块打包:开发阶段的 es6 打包 生产阶段的 Bundle.js
多类型模块支持: 开发阶段的 (.js .css .scss .hbs .png .ts) 打包 生产阶段的 (Bundle.js .css .png)
- 新特性代码编译
- 模块化 JavaScript 打包
- 支持不同类型的资源模块
模块打包工具 概要
webpack 模块加载器 (Loader) 代码拆分 (Code Splitting) 资源模块 (Asset Module)
打包工具解决的是前端整体的模块化,并不单值 Javascript 模块化
在安装一个要打包到生产环境的安装包时,你应该使用 npm install --save,如果你在安装一个用于开发环境的安装包(例如,linter, 测试库等),你应该使用 npm install --save-dev。请在 [npm 文档](https://docs.npmjs.com/cli/install) 中查找更多信息。
Webpack 快速上手
-
yarn init --yes
-
yarn add webpack webpack-cli --dev
-
yarn webpack --version
-
yarn webpack
"scripts": { "build": "webpack" //直接用build命令执行 },
不需要type="module",直接引用生成的dist下的目录文件
Webpack 配置文件
'src/index.js' -> 'dist/main.js' 根目录下创建webpack.config.js
const path = require('path')
module.exports = {
entry: './src/main.js', //指定入口文件路径,相对路径./是不能省略的
output: { //输出文件的位置
filename: 'bundle.js', //输出对象的名称
path: path.join(__dirname, 'output') //输出对象的绝对路径
}
}
Webpack 工作模式
- 这个属性有三种取值,分别是 production、development 和 none。
- 生产模式下,自动优化打包结果;
- 开发模式下,自动优化打包速度,添加一些调试过程中的辅助;
- None 模式下,运行最原始的打包,不做任何额外处理
-
yarn webpack
-
yarn webpack --mode development
-
yarn webpack --mode none
参考网址:webpack.js.org/configurati… 或者通过mode: 'development', 去指定
Webpack 打包结果运行原理
快速折叠 Ctrl+k Ctrl+0
Webpack 资源模块加载
-
yarn add css-loader --dev
-
yarn add style-loader --dev
module: { rules: [ { test: /.css$/, use: [ //多个loader,从后往前执行 'style-loader', 'css-loader' ] } ] }
Loader 是 webpack 的核心特性 借助于Loader 就可以加载任何类型的资源
Webpack 导入资源模块
根据代码的需要动态导入资源
需要资源的不是应用,而是代码
Javascript 驱动整个前端应用
-
逻辑合理,JS确实需要这些资源文件
-
确保上线资源不缺失,都是必要的
新事物的思想才是突破点
Webpack 文件资源加载器 Data URLs 与 url-loader
file-loader { test: /.png$/, use: 'file-loader' }
-
yarn add file-loader --dev publicPath: 'dist/' //网站的根目录
Data URLs data: [<mediatype>][;base64], <data> 协议; 媒体类型和编码 文件内容 data:text/html;charset=UTF-8,<h1>html content</h1> data:image/png;base64,iVBORw0KGgoAAAANSUhE...SuQmCC
url-loader
-
yarn add url-loader --dev
-
小文件使用 Data URLs, 减少请求次数
-
大文件单独提取存取,提高加载速度
-
超出10kb 文件单独提取存放
-
小于10kb 文件转换为 Data URLs 嵌入代码中 { test: /.png$/, use: { loader: 'url-loader', //适合小文件 options: { //添加配置属性 limit: 10 * 1024 // 10 KB 以下的文件转换,以上的文件交给file-loader } } }
Webpack 常用加载器分类
编译转换类 foo.css css-loader bundle.js 以JS形式工作的CSS模块
文件操作类
bar.png file-loader bundle.js 导出文件访问路径 bar.png
代码检查类
baz.js eslint-loader 检查通过/不通过 baz.js
Webpack 与 ES2015
因为模块打包需要,所以处理import 和 export
-
yarn add babel-loader @babel/core @babel/preset-env --dev
-
webpack 只是打包工具
-
加载器可以用来编译转换代码
{ test: /.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] //这个集合包含了全部的最新的es特性 } } },
Webpack 模块的加载方式
遵循 ES Modules 标准的 import 声明
遵循 CommonJS 标准的 require 函数
遵循 AMD 标准的 define 函数和 require 函数
样式代码中@import 指令和 url函数
HTML 代码中图片标签的 src 属性
-
yarn add html-loader --dev
{ test: /.html$/, use: { loader: 'html-loader', options: { attrs: ['img:src', 'a:href'] } } }
Webpack 核心工作原理
bundle your assets
Loader 机制是 Webpack 的核心
Webpack Loader的工作原理
markdown-loader
module.exports=""
Source -> md-loader -> (other-loader) -> Result(javascript代码)
-
yarn add marked --dev
-
yarn add html-loader --dev
{ test: /.md$/, use: [ 'html-loader', './markdown-loader' ] }
const marked = require('marked')
module.exports = source => {
// console.log(source)
// return 'console.log("hello ~")'
const html = marked(source)
// return html
// return `module.exports = "${html}"`
// return `export default ${JSON.stringify(html)}` 先将字符串转换为标准的json字符串
// 返回 html 字符串交给下一个 loader 处理
return html
}
Loader 负责资源文件从输入到输出的转换
对于同一个资源可以依次使用多个Loader
css-loader -> style-loader
Webpack 插件机制
目的:增强webpack 自动化能力 Loader 专注实现资源模块加载 Plugin 解决其他自动化工作 e.g. 清除 dist 目录 e.g. 拷贝静态文件至输出目录 e.g. 压缩输出代码 webpack + Plugin 实现大多前端工程化工作
Webpack 常用插件 clean-webpack-plugin
自动清理输出目录的插件,如dist目录
-
yarn add clean-webpack-plugin --dev
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
plugins: [ //专门配置插件的地方 new CleanWebpackPlugin() //创建一个实例,放到数组中 ]
Webpack 常用插件 html-webpack-plugin
选项 多实例 通过webpack 输出 HTML 文件
- yarn add html-webpack-plugin --dev
const HtmlWebpackPlugin = require('html-webpack-plugin')
// publicPath: 'dist/' 这个配置就不需要了
根目录下就不需要HTML文件了
plugins: [
new CleanWebpackPlugin(),
// 用于生成 index.html
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width'
},
template: './src/index.html'
}),
// 用于生成 about.html
new HtmlWebpackPlugin({
filename: 'about.html'
})
]
同时输出多个页面文件
plugins: [
new CleanWebpackPlugin(),
// 用于生成 index.html
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width'
},
template: './src/index.html' //HTML模板文件
}),
// 用于生成 about.html
new HtmlWebpackPlugin({ //加入多个实例对象,生成多个HTML文件
filename: 'about.html'
})
]
Webpack 常用插件 copy-webpack-plugin & 总结
- yarn add copy-webpack-plugin --dev
const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins: [
new CleanWebpackPlugin(),
// 用于生成 index.html
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample',
meta: {
viewport: 'width=device-width'
},
template: './src/index.html' //HTML模板文件
}),
// 用于生成 about.html 加入多个实例对象
new HtmlWebpackPlugin({
filename: 'about.html'
}),
new CopyWebpackPlugin([
// 'public/**'
'public' //public目录下所有文件拷贝到输出目录
])
]
社区还提供了很多插件
需求 -》 关键词 -》 搜索
开发一个插件
相比于 Loader, Plugin拥有更宽的能力范围 Plugin 通过钩子机制实现 一个函数或者是一个包含 apply 方法的对象
class MyPlugin { //移除webpage注释插件的过程
apply (compiler) {
console.log('MyPlugin 启动')
compiler.hooks.emit.tap('MyPlugin', compilation => { //hooks访问,tap方法注册钩子函数
// 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 //webpack内部要求必须的方法
}
}
}
})
}
}
new MyPlugin()
通过在生命周期的钩子中挂载函数实现扩展
多个任务
Webpack 开发体验
编写源代码 webpack 打包 运行应用 刷新浏览器 会降低开发效率 设想开发环境: 1.以HTTP Server 运行 2.自动编译 + 自动刷新 3.提供 Source Map 支持
Webpack 增强开发体验
自动编译 watch 工作模式 监听文件变化,自动重新打包 - yarn webpack --watch 以监视模式去运行 - serve dist 以http的形式运行应用
自动刷新浏览器
BrowserSync
- browser-sync dist --files "**/*"
操作上麻烦了,效率上降低了,多出2不磁盘读写操作
Webpack Dev Server
提供用于开发的 HTTP Server 集成[自动编译] 和 [自动刷新浏览器] 等功能
-
yarn add webpack-dev-server --dev
-
yarn webpack-dev-server 运行命令 将打包结果暂时存放到内存当中
-
yarn webpack-dev-server --open 自动唤醒浏览器,打开页面地址
可以一边编码,一边及时预览
Webpack Dev Server静态资源访问
Dev Server 默认只会serve打包输出文件 只要是webpack 输出的文件,都可以直接被访问 其他静态资源文件也需要 serve
contentBase
额外为开发服务器指定查找资源目录
devServer: {
contentBase: './public', //指定额外的静态资源路径,字符串或者数组
}
// // 开发阶段最好不要使用这个插件
// new CopyWebpackPlugin(['public']) 留在上线前的那次打包中使用
Webpack Dev Server代理API服务
跨域资源共享(CORS) 使用 CORS 的前提是API必须支持 并不是任何情况下 API 都应该支持 如果前后端 同源部署, 域名,协议,端口
问题:开发阶段接口跨域问题
Webpack Dev Server 支持配置代理
目标:将 GitHub API 代理到开发服务器
Endpoint 可以理解为 接口端点/入口
devServer: {
contentBase: './public', //指定额外的静态资源路径,字符串或者数组
proxy: { //添加代理服务配置
'/api': { //请求路径前缀
// http://localhost:8080/api/users -> https://api.github.com/api/users
target: 'https://api.github.com',
// http://localhost:8080/api/users -> https://api.github.com/users
pathRewrite: { //代理路径的重写
'^/api': ''
},
// 不能使用 localhost:8080 作为请求 GitHub 的主机名
changeOrigin: true //以实际代理的主机名作为请求
}
}
},
// ======================== fetch proxy api example main.js========================
const ul = document.createElement('ul')
document.body.append(ul)
// 跨域请求,虽然 GitHub 支持 CORS,但是不是每个服务端都应该支持。
// fetch('https://api.github.com/users')
fetch('/api/users') // http://localhost:8080/api/users
.then(res => res.json())
.then(data => {
data.forEach(item => {
const li = document.createElement('li')
li.textContent = item.login
ul.append(li)
})
})
(https://www.example.com/index.html)
|
v
(https://www.example.com/api/users)
(https://localhost/index.html)
|
v
(https://www.example.com/api/users)
Source Map
运行代码与源代码之间完全不同,如果需要调试应用,错误信息无法定位,调试和报错都是基于运行代码 source Source Map(映射关系) Compoled
//# sourceMappingURL=jquery-3.4.1.min.map 放在.min.js最后面,自动请求这个文件生成.min.map,根据这个文件的内容逆向解析出来的源代码,以便调试
Source Map 解决了源代码与运行代码不一致所产生的问题
Webpack 配置Source Map
devtool: 'source-map', //与Source Map相关的功能配置 webpack支持12种不同的打包方式,每种方式的效率和效果各不相同
eval 模式下的Source Map
devtool: 'eval',
打包后端模块代码,知道对应的源代码,只能定位源代码的名称不知道行内信息
不同 devtool 之间的差异
准备工作 具体对比 const HtmlWebpackPlugin = require('html-webpack-plugin') //给每个打包生成HTML文件
const allModes = [
'eval', //将模块代码放到eval函数当中去执行,通过source url标注文件的路径,并没有生成对应的source map,只能定位哪个文件出了错误
'cheap-eval-source-map', //eval函数当中去执行,可以定位错误的文件,只能定位行,经过es6转换后的结果,加工
'cheap-module-eval-source-map', //eval函数当中去执行,可以定位错误的文件,只能定位行,定位源代码跟我们编写的是一摸一样的;module没有经过loader加工
'eval-source-map', //eval函数当中去执行,可以定位错误的文件,还可以定位行和列错误的信息,生成对应的source map
'cheap-source-map', //没有eval方式去执行模块代码,没有module经过loader处理过
'cheap-module-source-map',
'inline-cheap-source-map',
'inline-cheap-module-source-map',
'source-map',
'inline-source-map', //data url方式嵌入,最不可能用到的
'hidden-source-map', //看不到source-map的效果,确实生成了source-map文件,代码中并没有引入这个文件;开发第三方包的时候有用
'nosources-source-map' //能看到错误出现的位置,点击错误信息,进去是看不到源代码的,提供了行和列的信息。为了生产环境中保护我们的源代码不会被暴露
]
//yarn webpack 会生成所有模式下的HTML
//serve dist
module.exports = allModes.map(item => { //对数组单独打包
return {
devtool: item, //遍历模式的名称
mode: 'none', //webpack内部不做处理
entry: './src/main.js', //入口
output: { //输出
filename: `js/${item}.js`
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader', //辨别其中一类模式的差异
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
filename: `${item}.html`
})
]
}
})
// module.exports = [
// {
// entry: './src/main.js',
// output: {
// filename: 'a.js'
// }
// },
// {
// entry: './src/main.js',
// output: {
// filename: 'b.js'
// }
// }
// ]
选择合适的 Source Map
视频中个人经验 开发模式 cheap-module-eval-source-map 1、我的代码每行不会超过80个字符 2、我的代码经过 Loader 转换过后的差异较大 (调整转换前的源代码) 3、首次打包速度慢无所谓,重写打包相对较快 (webpack serve监视模式重新打包,不是每次启动打包) 生产模式 none 1、Source Map 会暴露源代码 2、调试是开发阶段的事情 3、对代码没有信心的话:nosources-source-map
没有绝对选择
理解不同模式的差异,适配不同的环境
自动刷新的问题
办法1:代码中写死编辑器的内容 办法2:额外代码实现刷新前保存,刷新后读取
核心问题:自动刷新导致的页面状态丢失
页面不刷新的前提下,模块也可以及时更新
HMR 介绍
Hot Module Replacement 模块热替换,应用运行过程中实时替换摸个模块,应用运行状态不受影响 热拔插,在一个正在运行的机器上随时插拔设备
自动刷新导致页面状态丢失
热替换只将修改的模块实时替换到应用中
HMR 是 Webpack 中最强大的功能之一
极大程度的提高了开发者的工作效率
开始 HMR
集成在 webpack-dev-serve 中 webpack-dev-serve --hot 也可以通过配置文件开启
devServer: {
hot: true
// hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading
},
const webpack = require('webpack')
new webpack.HotModuleReplacementPlugin()
- yarn webpack-dev-serve --open css文件可以了,js有些问题
HMR 解惑
webpack 中的 HMR 并不可以开箱即用 Webpack 中的 HMR 需要手动处理模块热替换逻辑
1.为什么样式文件的热更新开箱即用
样式文件已经loader了
2.凭什么样式可以自动处理
js导出的成员各不相同
3.我的项目没有手动处理,JS照样可以热替换
你使用了某个框架,框架下的开发,每种文件都是有规律的;通过脚手架创建的项目内部都集成了 HMR 方案
总结:我们需要手动处理 JS 模块更新后的热替换
HMR API
devServer: { hot: true },
module.hot.accept('./editor', () => { //用于注册某一个模块更新后的处理函数;依赖路径,依赖路径后的处理函数;
console.log('editor 模块更新了,需要这里手动处理热替换逻辑')
})
- yarn webpack-dev-serve
JS 模块热替换
let lastEditor = editor module.hot.accept('./editor', () => { //用于注册某一个模块更新后的处理函数;依赖路径,依赖路径后的处理函数; // console.log('editor 模块更新了,需要这里手动处理热替换逻辑') // console.log(createEditor)
const value = lastEditor.innerHTML
document.body.removeChild(lastEditor)
const newEditor = createEditor()
newEditor.innerHTML = value
document.body.appendChild(newEditor)
lastEditor = newEditor
})
没有通用的
图片模块热替换
module.hot.accept('./better.png', () => { //注册图片模块热替换处理的函数 img.src = background console.log(background) })
HMR 注意事项
1.处理 HRM 的代码报错会导致自动刷新 devServer: { // hot: true hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading },
- yarn webpack-dev-serve
2.没启用 HMR 的情况下,HMR API 报错
if (module.hot) { }
热替换关闭,移除插件,yarn webpack,找到打包生成的bundle.js文件,处理热替换被移除掉了
生产环境优化
生产环境和开发环境有很大的差异 生产环境注重运行效率 开发环境注重开发效率
模式(mode)
为不同的工作环境创建不同的配置
不同环境下的配置
1.配置文件根据环境不同到处不同配置 2.一个环境对应一个配置文件
不同环境下的配置文件
1.配置文件根据环境不同到处不同配置 module.exports = (env, argv) => { //env通过cli传递环境名参数 argv运行cli过程中传递的所有参数 if (env === 'production') { //约定生产环境 config.mode = 'production' config.devtool = false //禁用掉source map config.plugins = [ ...config.plugins, new CleanWebpackPlugin(), //开发阶段可以省略的插件 new CopyWebpackPlugin(['public']) //开发阶段可以省略的插件 ] }
return config
}
yarn webpack 开发模式打包
yarn webpack --env production 会以生产模式进行打包
2.一个环境对应一个配置文件
大型项目,不同环境对应不同配置文件
根目录创建webpack.common.js webpack.dev.js webpack.prod.js
- yarn add webpack-merge --dev
const merge = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
})
- yarn webpack --config webpack.prod.js
可以以生产环境打包应用
DefinePlugin
为代码注入全局成员 process.env.NODE_ENV
plugins: [
new webpack.DefinePlugin({
// 值要求的是一个代码片段
API_BASE_URL: JSON.stringify('https://api.example.com')
})
]
Tree Shaking
[摇掉]代码中未引用部分(dead-code) 比如:console.log()。没有引用到函数 - yarn webpack --mode production 会在生产模式下自动开启
Tree Shaking 使用
Tree Shaking 不是指某个配置选项,是一组功能搭配使用后的优化效果,production 模式下自动开启 usedExports 负责标记 [枯树叶] minimize 负责 [摇掉] 他们
optimization: { //集中配置webpack内部优化的一些功能
// 模块只导出被使用的成员
usedExports: true,
// 尽可能合并每一个模块到一个函数中, Scope Hoisting
concatenateModules: true,
// 压缩输出结果
// minimize: true
}
- yarn webpack
合并模块函数 Scope Hoisting
// 尽可能合并每一个模块到一个函数中, Scope Hoisting concatenateModules: true,
Tree Shaking & Babel
Tree Shaking 前提是 ESModules ,由webpack 打包的代码必须使用 ESM 为了转换代码中 ECMAScript 新特性 选择babel-loader 处理js ES Modules -> CommonJS
options: {
presets: [
// 如果 Babel 加载模块时已经转换了 ESM,则会导致 Tree Shaking 失效
// ['@babel/preset-env', { modules: 'commonjs' }] //强制使用es babel 插件,转换成mommon js
// ['@babel/preset-env', { modules: false }] //不会开启 转换的插件
// 也可以使用默认配置,也就是 auto,这样 babel-loader 会自动关闭 ESM 转换。auto根据环境去判断是否开启ES Modules 插件
['@babel/preset-env', { modules: 'auto' }] //配置名称,配置对象
]
}
sideEffects 副作用
副作用:模块执行时除了导出成员之外所作的事情 sideEffects 一般用于 npm 包标记是否有副作用
开启
optimization: {
sideEffects: true,
// 模块只导出被使用的成员
// usedExports: true,
// 尽可能合并每一个模块到一个函数中
// concatenateModules: true,
// 压缩输出结果
// minimize: true,
}
// "sideEffects":false 标识没有副作用
sideEffects 注意
使用前:确保你的代码真的没有副作用
"sideEffects": [ //标识副作用文件
"./src/extend.js",
"*.css"
]
代码分割
所有代码最终都被打包到一起 bundel 体积过大 并不是每个模块在启动时都是必要的 分包,按需加载
Code Splitting 分包/代码分割
1.多入口打包
2.动态导入
多入口打包 Multi Entry
多页应用程序:一个页面对应一个打包入口,公共部分单独提取
entry: { //定义对象
index: './src/index.js',
album: './src/album.js'
},
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'] //形成独立的
})
]
提取公共模块 Split Chunks
不同入口肯定会有公共模块 optimization: { splitChunks: { // 自动提取所有公共模块到单独 bundle chunks: 'all' } }, 生成 album~index.bundle.js 公共文件
动态导入 Dynamic Lmports
按需加载,需要用到某个模块时,再加载这个模块 动态导入的模块会被自动分包
if (hash === '#posts') {
// mainElement.appendChild(posts())
import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {
mainElement.appendChild(posts())
})
} else if (hash === '#album') {
// mainElement.appendChild(album())
import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) => {
mainElement.appendChild(album())
})
}
魔法注释 Magic Comments
灵活处理动态加载的模块,输出时生成的文件 /* webpackChunkName: 'components' */
MiniCssExtractPlugin 提取CSS文件到单独文件中
-
yarn add mini-css-extract-plugin --dev const MiniCssExtractPlugin = require('mini-css-extract-plugin')
new MiniCssExtractPlugin()
rules: [ { test: /.css$/, use: [ // 'style-loader', // 将样式通过 style 标签注入 MiniCssExtractPlugin.loader, //考虑是否超过150kb才用 'css-loader' ] } ]
OptimizeCssAssetsWebpackPlugin 压缩CSS
- yarn add optimize-css-assets-webpack-plugin --dev
- yarn add terser-webpack-plugin --dev
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
optimization: {
minimizer: [
new TerserWebpackPlugin(), //
new OptimizeCssAssetsWebpackPlugin()
]
},
生产模式运行打包,才会压缩 - yarn webpack --mode production
输出文件名 Hash substitutions
生产模式下,文件名使用 Hash
1.[name]-[hash].bundel.js 都会发生变化
2.[name]-[chunkhash].bundel.js 对应文件发生变化,解决缓存问题
3.[name]-[contenthash].bundel.js 对应文件发生变化
4.[name]-[xx:8].bundel.js 指定长度
Rollup
更为小巧,仅仅是一款 EXM 打包器 Rollup 中并不支持类似 HMR 这种高级特性 提供一个充分利用 ESM 各项特性的高效打包器
Rollup快速上手
- yarn add rollup --dev
- yarn rollup
- yarn rollup ./src/index.js
- yarn rollup ./src/index.js --format iife
- yarn rollup ./src/index.js --format iife --filedist/bundle.js
Rollup配置文件
根目录创建 rollup.config.js 文件
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js', //输出文件名
format: 'iife' //输出格式
}
}
- yarn rollup --config //声明启用的是config文件
- yarn rollup --config rollup.config.js
Rollup使用插件
加载其他类型资源模块 导入 Common JS 模块 编译 ECMAScript 新特性 Rollup 支持使用插件的方式扩展,插件是rollup 唯一扩展途径 rollup-plugin-json
- yarn add rollup-plugin-json --dev
import json from 'rollup-plugin-json'
plugins: [
json()
]
- yarn rollup --config
Rollup加载NPM 模块
rollup-plugin-node-resolve
- yarn add rollup-plugin-node-resolve --dev
import resolve from 'rollup-plugin-node-resolve'
resolve()
// 导入模块成员
import _ from 'lodash-es'
log(_.camelCase('hello world'))
- yarn rollup --config
Rollup Common JS 模块
rollup-plugin-commonjs
- yarn add rollup-plugin-commonjs --dev
import commonjs from 'rollup-plugin-commonjs'
commonjs()
//创建cjs-module.js 文件
module.exports = {
foo: 'bar'
}
//index.js 文件导入和使用
import cjs from './cjs-module'
log(cjs)
Rollup代码拆分
Dynamic Imports
//index.js 动态导入
import('./logger').then(({ log }) => {
log('code splitting~')
})
- yarn rollup --config --format amd //覆盖启用amd
dir: 'dist', //可以输出多个文件
format: 'amd'
- yarn rollup --config
Rollup多入口打包
export default { // input: ['src/index.js', 'src/album.js'], input: { foo: 'src/index.js', bar: 'src/album.js' }, output: { dir: 'dist', format: 'amd' } }
- yarn rollup --config
<!-- AMD 标准格式的输出 bundle 不能直接引用 -->
<!-- <script src="foo.js"></script> -->
<!-- 需要 Require.js 这样的库 -->
<script src="https://unpkg.com/requirejs@2.3.6/require.js" data-main="foo.js"></script>
选用原则 Rollup/Webpack
优点:
- 输出结果更加扁平
- 自动一簇未引用代码
- 打包结果依然完全可读 缺点:
- 加载非 ESM 的第三方模块比较复杂
- 模块最终都被打包到一个函数中,无法实现 HMR
- 浏览器环境中,代码拆分功能依赖 AMD 库
如果我们正在开发应用程序, 有所欠缺
如果我们正在开发一个框架或者类库,有点有必要,缺点可以忽略
大多数知名框架/库都在使用 Rollup
社区中希望二者并存
总结:webpack 大而全, Rollup 小而美
引用开发使用 webpack
库/框架开发使用 Rollup
Parcel 零配置前端应用打包器
-
yarn init
-
yarn add parcel-bundler --dev
-
yarn parcel src/index.html //浏览器自动刷新
// import $ from 'jquery' //自动安装依赖 import foo from './foo' import './style.css' //加载第三方 import logo from './zce.png' foo.bar() import('jquery').then($ => { //动态导入 $(document.body).append('<h1>Hello Parcel</h1>') $(document.body).append(`<img src="${logo}" />`) }) if (module.hot) { //支持热替换 module.hot.accept(() => { //只可以接收一个参数 console.log('hmr') }) } -
yarn parcel build src/index.html //以生产模式运行打包
总结:体验感觉舒服,2017年发布,构建速度更快,完成零配置 webpack 有更好的生态,越来越好用。