webpack
1.Commonjs 与 ESModule
1.1 ESModule特性
1.模块化规范
1.自动采用严格模式,忽略”use strict“
2.每个 ESM 模块都是单独的私有作用域
3.ESM 是通过 CORS 去请求外部 JS 模块的
<script src="http://114.55.108.177:8080/" type="module">// 存在跨域 报错
</script>
4.ESM 的 script 标签会延迟执行脚本(异步),不会阻塞 script 标签下面的语句
5.commonjs 规范是 node 中提出的规范,commonjs 约定的是一套同步加载的规范。node在启动时加载模块,在运行时使用模块。但是在浏览器中使用同步会出现 性能 问题
<body>
<!-- <script src="http://114.55.108.177:8080/" type="module"></script> -->
<script type="module">
alert('我被阻塞了')
</script>
<!-- 页面会显示 aaaa 后在执行上面 module 语句 -->
<div>aaaa</div>
</body>
2.ESM 导入导出
ESM 导出 export {a,b} 这里导出的是对应的地址,而不是ES6对象简写形式去导出对象、函数或是变量。
ESM 导入 import {a,b} from 'xxx地址' 这里引入的是对应关系,而不是ES6解构赋值(该对象、函数或是变量。详情可见:1.2 Commonjs 特性),所以引入的值是不能修改的,比如 :a = 10 ,这里会报错。这样的引入完成了模块化功能
在ESM导出的空间里改变 a,b的值,导入的值也会随之修改。说明了:导入的值是一段引用关系
导出变量的方式,使用默认导出 export defult {a,b}
// a.js 文件
export let a = 100 //定义一个 a 变量并且导出
setTimeout(()=>{
a = 200 // 一秒后给 a 赋值
},1000)
// b.js 文件
import { a } from './a.js'
console.log(a) //引入a并且打印 a = 100
setTimeout(() => {
//a模块有一个定时器,并且在一秒后将 a 赋值成 200,我们在两秒后打印它
console.log(a) //两秒后 a = 200
}, 2000)
esm 动态导入:使用 import方法 ,该方法是个promise函数,使用.then获取成功的回调。根据需要来动态传参
// a.js 文件
export default { a,b } //导出 a,b
// b.js 文件
import("./a.js").then((port)=>{
// method是导入模块,成功回调时自行赋值。使用导入模块
console.log(port.a)
})
esm 同时导入默认非默认成员
import {a,b,defult as c} from "xxx地址" //这样同时导入了默认和非默认成员。
import c,{a,b} from "xx地址" //第二种写法: 这样导入,c指的是 "xx地址中的默认成员"
ESM 一次性导入所有成员
import {* as all} from "xx地址" //这样 all 中包含所有成员
esm导出,将导入的成员直接导出,那么导入后导出的成员无法在该导入作用域下使用。
export {a,b} from "导出文件的地址" //导入后的成员,再次导出
export default a from './a.js' //也可以默认导出
console.log(a) //导出后本地使用导入的成员会 报错
3.在 Node 中使用 ESM
Node 环境需要大于 8.5 ,创建文件的文件名为 .mjs
//创建文件目录
// 01_node使用ESM
//----a.mjs
//----b.mjs
// a.mjs
export const a = 100 //导出
console.log(a); // a = 100
// b.mjs
import { a } from './a.mjs'
console.log('我是b文件 引入a', a) // a = 100
##使用 node 运行 .mjs 文件时需要添加 --experimental-modules 语句
$ node --experimental-modules .\b.mjs
1.2 Commonjs 特性
1.模块化规范
Commonjs导出,默认导出为 export.defult = { k:value k导出成员,value导出值},也可以写成 export.k = value。两者的导出方式是等同的
export.defult = { k : value } // k导出成员,value导出值
export.k = value //两者相同
可以使用 ESM 导入 CommonJS 模块
// a.js
module.exports = {
foo:'commonJS export value' //CommonJS 中导出对象
}
// a.mjs
import foo from './a.js' // ESM 引入 CommonJS 中导出的对象
console.log(foo); // 结果为 { foo: 'commonJS export value' }
// ESM 无法使用对象的方式去引入 CommonJS 中的导出,会报错。说明 import 引入并不是对象解构赋值,而是单纯的导入语法
import {foo} from './a.js' // 报错
console.log(foo);
但是在 CommonJS 中通过 require() 模块载入ESModule也是不被允许的,会直接报错。
// b.mjs
export let b = 'bbb' //ESM 导出 b
// b.js
const b = require('./b.mjs') // CommonJS 中引入 ESM
console.log(b); //报错
2. ESM 中没有的 CommonJS 模块全局成员
CommonJS 的部分模块无法在 ESM 中使用
// a.js 在 CommonJS 中以下语句运行良好,但是在 ESM 中没有这些模块会报错
//加载模块函数
console.log(require)
// 模块对象
console.log(module)
//导出对象别名
console.log(exports)
//当前文件绝对路径
console.log(__filename)
//当前文件所在目录
console.log(__dirname)
// a.mjs 在 ESM 中实现相同方法
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
console.log(__filename) //获取当前路径
const __dirname = dirname(__filename)
console.log(__dirname) //获取相对路径
1.3 使用 package.json 配置 直接使用 ESM
在 package.json 配置 type 属性。设置文件运行环境
// package.json
{
"type": "module", //以 ESM 模块使用 目录文件
}
// a.js
import {b} from './b.js'
console.log(b) //输出 b
$ node --experimental-modules .\a.js //使用 ESM 的 .js文件
如何在 MSM 规范中使用 CommonJS 呢,只需要将文件名改为 .cjs
// b.js
const path = require('path')
console.log(path.join(__dirname,'COM'));
$ node --experimental-modules b.js // 报错 ReferenceError: require is not defined
// b.cjs
const path = require('path')
console.log(path.join(__dirname,'COM'));
$ node --experimental-modules b.cjs //成功输出当前目录 C:\Users\Administrator\Desktop\webpack\COM
2.Babel
安装 Babel
$ npm i babel-loader @babel/node @babel/core @babel/preset-env --dev
使用 babel 打包
module: {
rules: [
{
test: /.js$/,
use: {
loader: 'babel-loader', //babel
},
},
]
}
打包后发现 es6 语法并没有转换为 es5 语法,那是因为 babel 只是提供的一个平台,你还需要通过插件来转换具体的特性
module: {
rules: [
{
test: /.js$/,
use: {
loader: 'babel-loader', //babel
options: {
presets: ['@babel/preset-env'], // 使用 @babel/preset-env
},
},
},
]
}
3.使用 webpack 打包
1.1使用 webpack 打包一个程序
创建一个 webpack.config.js 文件,在文件配置打包属性
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/main.js', //入口文件
output: {
filename: 'index.js', // 打包后的文件名
path: path.join(__dirname, 'output'), //打包后存放的位置
},
}
1.2 Loader
Loader 专注于资源模块的加载,以及整体项目的打包
1.打包其它非js后缀的文件
处理 css 文件的打包,直接打包css文件会产生错误,需要配置 css-loader 。使用 npm 安装loader
$ npm install --save-dev css-loader //安装 css-loader
使用 loader 处理对应后缀的文件
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/index.css', //入口文件
output: {
filename: 'index.js', // 打包后的文件名
path: path.join(__dirname, 'output'), //打包后存放的位置
},
module:{
rules:[
{
test:/.css$/, // 指定打包结尾的文件
use:'css-loader' // 使用的 loader
}
]
}
}
这里我们发现 css-loader 将 css 文件转换成了 js 文件,可是页面并没有因为打包后的 js 而改变样式。那是因为打包后文件里的方法没有被使用。这时我们需要用到 style-loader 这个 loader 的作用,是将 css-loader 转换过后的结果,通过style标签的形式追加到页面上
安装 style-loader
$npm install --save-dev style-loader
使用 style-loader 转换 。 (需要注意配置了多个 Loader 它是从后向前执行。所以要把 css-loader 写到 style-loader 后面执行)
const path = require('path')
module.exports = {
mode: 'none',
entry: './src/index.css', //入口文件
output: {
filename: 'index.js', // 打包后的文件名
path: path.join(__dirname, 'output'), //打包后存放的位置
},
module:{
rules:[
{
test:/.css$/, // 指定打包结尾的文件
use:['style-loader','css-loader'] // 这里的loader 需要写成数组形式. 需要注意配置了多个 Loader 它是从后向前执行。所以要把 css-loader 写到 style-loader 后面执行。首先将其转换成 js 文件后在使用 style-loader
}
]
}
}
2.资源文件的打包 url-loader file-loader
在打包的过程中,我们更希望使用 javascript 引入样式或资源文件,与其建立依赖文件的关系。而不是直接打包单独 css 样式。这样做更有利于管理整个
项目。再逻辑上更为合理,能确保上线项目资源不会缺失,这些都是必要的。
// index.css 样式文件
.heading {
margin: 10px auto;
width: 800px;
height: 100px;
background-color: #000;
color: white;
text-align: center;
line-height: 100px;
}
// handle.js
import './index.css' //引入样式文件
//导出一个创建 dom 的方法
export default () => {
const dom = document.createElement('h2')
dom.className = 'heading' //设置类名 heading
dom.innerText = 'Hello webpack'
return dom
}
// main.js
import createHeading from './handle.js'
import icon form './icon.png' //引入资源文件
const dom = createHeading() //导入 dom
document.body.append(dom) //给 body 添加 dom
const img = new Image()
img.src = icon //添加图片地址
document.body.append(img) // 这里导入的资源需要配置 file-loader
// 注意点: handle.js文件中使用的是 ESM 如果有用到 require() 需要将文件名改为.cjs
//最后通过 npm run build 打包 样式实现成功
配置资源文件后可能会出现资源无法加载的问题,那是因为打包过后的 index.html 没有生成在 dist 目录,而是在项目跟目录下。所以无法找到项目文件。我们需要配置 publicPath 路径
url-loader 可以设置超出 10KB 文件单独提取存放 小于10KB 文件转换为 Data URLs 嵌入代码中
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'index.js', // 打包后的文件名
path: path.join(__dirname, 'dist'), //打包后存放的位置
publicPath:'dist/' //告诉网站,打包过后的资源文件所在位置
},
module: {
rules: [
{
test: /.css$/, // 指定打包结尾的文件
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpg|gif)$/i,
use: {
loader:'url-loader', //使用 url loader 打包成base64
options:{
limit: 10 * 1024 // 10kb 设置 limit 只会将 10kb 以下的问题打包成 base64 超出内容会调用 file-loader
}
},
},
],
},
}
注意事项:使用 url-loader 必须安装 file-loader 。对于超出指定大小的文件会调用 file-loader 。如果没有安装会报错
3.自定义 loader
引入一个 md 类型的文件,通过自定义 loader 进行处理
// index.js
import about from './about.md' //导入 md
const cli = document.createElement('div')
cli.innerHTML = about()
console.log(about())
document.body.append(cli)
自定义一个 loader 处理匹配文件名的数据
// markdown-loader.js
const marked = require('marked') //转换 html
module.exports = (source) => {
const html = marked.parse(source) // 将 md 文件转换成 html 代码
console.log('hello 啊 树')
//return 'hello ~' // 这里会报错,webpack执行loader时希望返回的结果是一个 javascript 代码,这里不是一个标准的javascript代码。
return `export default () => ${JSON.stringify(html)}` //html 文件已经是 html 格式,但是我们想导出一个 html 字符串可能会与 html 标签文件冲突。所以需要强转成 JSON
}
配置 loader
rules:[
{
test: /.md$/i,
// use: './markdown-loader', // loader 可以是本地路径
use: ['html-loader','./markdown-loader'], //也可以通过管道 ,使用其它loader去处理我们想要生成的文件
}
]
1.3 Plugin 插件
相比于 Lader,Plugin 拥有更宽的能力范围。Plugin 是一个函数或者是一个包含 apply 方法的对象。通过 webpack 的生命周期钩子中挂载函数实现扩展。获取打包的文件,对文件优化处理。
1.通过plugin实现自动化
Plugin 主要用于解决项目加载以外其它自动化工作。如:打包前清除 dist 目录,压缩输出代码
$npm i clean-webpack-plugin --dev
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode:"none",
plugins: [
new CleanWebpackPlugin(), // 这个插件会在打包时清除存在的 dist 目录
],
}
2.通过 webpack 输出 HTML 文件
通过 plugin 不需要手动硬编码 HTML,可以确保路径引用正常。
$ yarn add html-webpack-plugin --dev
const HTMLWebpackPlugins = require('html-webpack-plugin') //默认导出的是webpack 插件类型
module.exports = {
mode:"none",
plugins: [
new HTMLWebpackPlugins({
title: 'xxx后台管理项目', //生成的 html 文件标题
template: './src/index.html', //指定使用的模板文件
}), //自动生成 dist 目录和 index.html 文件
],
}
// 模板文件 ./src/index.html
<html lang="en">
<head>
<!-- 这里的title 会被打包成模板的 title 字段-->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>
// 打包后的文件 dist/index.html
<html lang="en">
<head>
<!-- 这里的 title 变成了 xxx后台管理项目 -->
<title>xxx后台管理项目</title>
<script defer src="index.js"></script></head>
<body>
<script src="./dist/index.js"></script>
</body>
</html>
3.输出多个 HTML 文件
new HTMLWebpackPlugins({
title: 'xxx后台管理项目', //生成的 html 文件标题
template: './src/index.html', //指定模板文件
}), //自动生成 dist 目录和 index.html 文件
new HTMLWebpackPlugins({
filename:'about.html'
}),//生成多个 html 文件
每个 HTMLWebpackPlugins 生成一个 html 文件,由于自定义的 HTML 模板没有被 js 文件使用
4.静态资源打包
我们希望将 pubilc 文件也打包到 输出目录
const CopyPlugin = require('copy-webpack-plugin')
new CopyPlugin({
patterns: [
{
// 'public/**'
from: 'public',
to: 'assets', //打包时会将 public 目录中所有文件拷贝到输出目录 assets 中
},
],
}),
5.自定义 plugin 插件
通过 webpack 的生命周期钩子中挂载函数实现扩展。获取打包的文件,对文件优化处理。
// 声明一个 plugin 其中必须包含 apply() 方法
class MyPlugin {
// 注册 plugin 会调用 apply 方法
apply(compiler) {
//通过 compiler 可以获取 webpack 的钩子函数
compiler.hooks.emit.tap('MyPlugin', (compilation) => {
// compilation => 可以理解为此次打包的上下文
for (const text in compilation.assets) {
// console.log(text) //获取的是打包文件的文件名 index.js index.html ...
console.log(compilation.assets[text].source()) // source() 获取文件的内容
//对文件名为js的文件进行处理
if (text.endsWith('.js')) {
//获取文件中的内容
const contents = compilation.assets[text].source()
const withoutComments = contents.replace(/\/\*\* + \*\//g, '') // 将所有内容中 /*****/ 替换为 ''
compilation.assets[text] = {
source: () => withoutComments, // 文件内容变成替换后的内容
size: () => withoutComments.length, // 必须指定文件长度
}
console.log(compilation.assets[text].source())
}
}
})
}
}
// webpack.config.js
plugins: [new MyPlugin()]
1.4 自动刷新和打包工具
1.安装使用 webpack-dev-server
webpack-dev-server 集成了 自动编译 和 自动刷新浏览器 等功能
$yarn add webpack-dev-server --dev
使用 webpack-dev-server 打开程序
$yarn webpack-dev-server -open
Dev Server 默认只会 serve 打包输出文件,只要是 webpack 打包输出的文件都会被直接访问 。其它的静态资源作为开发服务器资源被访问,就需要额外的告诉 webpack-dev-server
2.配置 devServer
devServer: {
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 作为请求的主机名
changeOrigin: true,
},
},
},
4.Source Map 源代码地图
1.1 Source Map 映射
代码在打包的过程中,会打包成压缩的代码格式。Source Map 可以映射代码和源代码的关系。通过转换过后的 Source Map 文件可以逆向解析源代码
源代码引入 //# sourceMappingURL=build.js.map
source Map 解决了源代码和运行代码不一致所产生的问题
//# sourceMappingURL = jquery.min.map
// webpack.config.js 配置
module.exports = {
mode: 'development',
entry: './src/index.js',
...
devtool: 'source-map', // 配置解析方式,一共 12 种
...
}
devtool 的可配置选项
没有通过 source-map 打包的压缩文件
经过 source-map 打包后的映射文件
5 HMR API
通过 HMR 配置后 , 可以使页面不刷新的情况下执行 webpack-dev-server。他可能用到缓存等方式
module.hot.accept('./handle', () => {
console.log('我更新了 handle') //handle 文件发生改变时打印
})
webpack-dev-server 集成了 HNR 插件
$yarn webpack-dev-server --hot ## 启动服务
webpack 配置热替换
module.exports = {
...
devServer:{
hot:true
}
...
}
1.1 处理 JS 模块热替换
// index.js
import createHeading from './handle.js' //handle.js 的返回值是一个 dom 元素
const dom = createHeading() //导入 dom
document.body.append(dom) //给 body 添加dom
// 记录旧的 dom 发生更新时,防止无法找到上一次更新的元素
let oldDom = dom
// handle.js 组件文件内容修改时触发
module.hot.accept('./handle.js', () => {
console.log('我更新了 handle') //handle 文件发生改变时打印
// 保存旧 dom 的 innerHTML 文本,在新dom修改后返回
const value = oldDom.innerHTML
document.body.removeChild(oldDom)
// 修改文件后 返回的dom
let newDom = createHeading()
// 渲染旧 dom 的缓存
newDom.innerHTML = value
//重构代码
document.body.append(newDom)
// 将文件修改后返回的dom 设置为旧dom
oldDom = newDom
})
以上代码会有 bug ,因为获取的是 innerHTML 整个DOM 元素。所以在修改文本时 'Hello webpack',不会对文本进行更新。如果需要更好的热替换效果,需要自行学习研究。
手动修改 handle.js 文件的dom,并且添加一个属性 ddd ,页面将不会刷新并渲染 ddd 属性
import './index.css'
//导出一个创建 dom 的方法
export default () => {
const dom = document.createElement('h2')
dom.className = 'heading'
dom.innerText = 'Hello webpack'
// 在js文件已经渲染后手动添加一个属性。 (手动修改文件)
dom.setAttribute('ddd', 100)
return dom
}
热更新前
热更新后
webpack 没有办法提供一个通用的 热替代方案
6. 生产环境优化
1.1 通过打包传入环境名,配置打包环境
webpack 在使用的过程中,在开发阶段时注重开发效率。在生产环境时更注重运行效率。webpack 推出了 mode 模块。通过传入打包模式,进行不同阶段的打包。
// webpack 支持导出一个函数
module.exports = (env, argv) => {
const config = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'index.js',
path: path.join(__dirname, 'dist'),
},
/* devServer: {
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 作为请求的主机名
changeOrigin: true,
},
},
}, */
devtool: 'eval',
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /\.css$/i, // 指定打包结尾的文件
use: ['style-loader', 'css-loader'], // 使用的 loader
},
{
test: /\.(png|jpg|gif)$/i,
use: {
loader: 'url-loader', //使用 url loader 打包成base64
options: {
limit: 10 * 1024, // 10kb 设置 limit url-loader 只会将 10kb 以下的问题打包成 base64
},
},
},
{
test: /.md$/i,
use: './markdown-loader', // loader 可以是本地路径
},
],
},
plugins: [
new HTMLWebpackPlugins({
title: 'xxx后台管理项目', //生成的 html 文件标题
template: './src/index.html', //指定模板文件
}), //自动生成 dist 目录和 index.html 文件
new HTMLWebpackPlugins({
filename: 'about.html',
}), //生成多个 html 文件
// new MyPlugin(),
],
}
//如果打包时使用的生产模式,就会使用生产打包。使用打包时携带 --env xxx 通过env可以获得 xxx属性
if (env.production) { //{ WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, prod: true }
config.mode = 'production'
config.devtool = false
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(), //生产环境时打包后清除 dist 目录
new CopyPlugin({
patterns: ['public'], //生产环境将静态文件打包到 public 目录
}),
]
}
return config
}
if 判断需要在打包时传入属性,进行判断
$ yarn webpack --env prod ## 上文 env.prod 的值为true,上文判断的是 if (env.production) 所以需要传入 production 区分环境
$ yarn webpack --env production ## 当前打包处于生产模式
1.2 通过不同环境的配置文件,配置打包环境
配置三个环境的文件,通过映射实现不同环境的打包。因此我们分别定义三个文件
##定义三个环境配置文件,实现分别打包
# webpack.common.js 配置源文件
# webpack.dev.js 开发环境
# webpack.prod.js 生产环境
# 由于文件定义缺少了 webpack.config.js webpack无法识别打包文件,需要通过 --config 文件名,使用对应打包
# yarn webpack --config webpack.dev.js 开发模式打包
# yarn webpack --config webpack.prod.js 生产模式打包
webpack.common.js 配置源文件 。导出默认配置
const HTMLWebpackPlugins = require('html-webpack-plugin') //默认导出的是webpack 插件类型
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'index.js',
path: path.join(__dirname, 'dist'),
},
devtool: 'eval',
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /\.css$/i, // 指定打包结尾的文件
use: ['style-loader', 'css-loader'], // 使用的 loader
},
{
test: /\.(png|jpg|gif)$/i,
use: {
loader: 'url-loader', //使用 url loader 打包成base64
options: {
limit: 10 * 1024, // 10kb 设置 limit url-loader 只会将 10kb 以下的问题打包成 base64
},
},
},
{
test: /.md$/i,
use: './markdown-loader', // loader 可以是本地路径
},
],
},
plugins: [
new HTMLWebpackPlugins({
title: 'xxx后台管理项目', //生成的 html 文件标题
template: './src/index.html', //指定模板文件
}), //自动生成 dist 目录和 index.html 文件
],
}
webpack.prod.js 配置生产环境文件,需要设置静态资源。可以单独写在生产环境文件中
const common = require('./webpack.common') // 获取开发打包配置
const { merge } = require('webpack-merge') // 载入合并配置函数
const { CleanWebpackPlugin } = require('clean-webpack-plugin') //删除 dist 目录重新打包
const CopyPlugin = require('copy-webpack-plugin') //复制静态资源文件
// merge 函数会自动处理合并的逻辑
module.exports = merge(common, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(), //重新打包时删除之前的 dist 目录
new CopyPlugin({
patterns: [{ from: 'public', to: 'public' }],
}), //复制 public 文件夹
],
})
webpack.dev.js 开发环境如果无需修改,可以直接导出common 文件
module.exports = require('./webpack.common') // 获取开发打包配置
最后可以将构建的命令写入 package.json 中
{
"scripts": {
"build": "webpack --config webpack.prod.js",
"dev":"webpack --config webpack.dev.js"
},
}
1.3 webpack.DefinePlugin
使用 webpack.DefinePlugin 定义的键值会被注入到代码中
// webpack.config.js
const webpack = require('webpack')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
},
plugins: [
new webpack.DefinePlugin({
API_BASE_URL: JSON.stringify('http://114.55.108.177:8080'), //提供了一个 API_BASE_URL 的全局变量
}),
],
}
// src/main.js
//通过 webpack 配置 url
console.log(API_BASE_URL) //http://114.55.108.177:8080
1.4 Tree ShaKing
Tree ShaKing 会在生产模式下自动启动。手动配置 webpack 在开发模式中使用
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
},
optimization:{
usedExports:true, //负责标记 [枯叶] ,只导出外部使用过的成员
minimize:true //压缩输出结果
}
}
Tree Shaking 的前提是 ESM 环境 ,由 webpack 打包的代码必须使用 ESM 。在使用 webpack 进行打包时,我们经常会使用到 babel 插件,而babel 处理代码时很有可能将 ESM 处理成 CommonJS 导致Tree ShaKing 失效
1.5 sideEffects 副作用文件
正常情况下(非生产模式 )系统不会检测模块是否有副作用。当开启 sideEffects 时。系统将不会打包引入而没有使用的模块
//webpack.config.js
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
},
optimization:{
//以下配置生产模式下自动开启
usedExports:true, //负责标记 [枯叶] ,只导出外部使用过的成员
minimize:true, // js 代码压缩
concatenateModules: true, //尽可能合并每一个模块到一个函数中
sideEffects:true // import 引入文件未使用不会被打包
}
}
package.json 这里的sideEffects 和 webpack 的意思不同。
// package.json
{
"name":"xxx",
"version":"xxx",
...
//"sideEffects":false //这里是标识代码是没有副作用的
"sideEffects":[
"*.css" //指定有副作用文件,有副作用文件会被打包进dist目录
]
}
1.6 分别打包
1. 多入口打包
配置 entry 为对象类型,对文件分别打包
// webpack.config.js
entry: { // 配置多个入口打包
index: './src/main.js',
put: ['./src/a.js', './src/b.js', './src/c.js'], // 文件名
},
output: {
filename: 'bundle.js', // 打包文件名
},
上面配置 html-webpack-plugin 打包后可能出现 html 导入多条 srcipt 情况
<!-- 导出文件 index.html -->
<!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" />
<title>index</title>
<script defer="defer" src="index.bundle.js"></script>
<script defer="defer" src="put.bundle.js"></script>
</head>
<body></body>
</html>
我们可以配置多个 html 文件,分别引入打包出的文件
module.exports = {
mode: 'none',
entry: {
index: './src/main.js',
put: ['./src/a.js', './src/b.js', './src/c.js'],// a b c 文件合并导出
},
output: {
filename: '[name].bundle.js', // 根据 entry 属性文件名分别导出js文件
},
plugins: [
// 配置多个html 分别引入 js 文件
new HTMLWebpackPlugins({
title: 'index', //生成的 html 文件标题
template: './src/index.html', //指定导出模板文件
filename: 'index.html',
chunks: ['index'], // //设置 html srcipt 的 src 引入 entry.index 的属性
}), //自动生成 dist 目录和 index.html 文件
new HTMLWebpackPlugins({
title: 'abc', //生成的 html 文件标题
template: './src/index.html', //指定导出模板文件
filename: 'put.html',
chunks: ['put'], //设置 html srcipt 的 src 引入 entry.put 的属性
}), //自动生成 dist 目录和 index.html 文件
],
optimization: {
usedExports: true, // 标记枯叶
minimize: false, // 代码亚索
sideEffects: true, // 检测文件是否有副作用
},
}
2.提取公共模块
对引入的大文件单独打包成一个文件,如 jquery 。自定义的组件
// a.js
import $ from 'jquery'
console.log($)
//b.js
import $ from 'jquery'
console.log($)
上面的两个 js 文件分别引入了 jquery 库。我们配置 webpack.config.js 对它进行提取打包到单独文件中
optimization: {
usedExports: true,
minimize: true,
sideEffects: true, // 检测文件是否有副作用
splitChunks: {
//import 引入的大文件会被单独打包成一个 js 导出
chunks: 'all',
},
},
打包后 jquery 库 被分配到单独的js文件中
3.动态导入组件
1.import() 动态导入
import 命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行。所以 import
和export 命令只能在模块的顶层,不能在代码块之中
// 报错 js 在编译时不会分析执行 if
if (x === 2) {
import MyModual from './myModual';
}
2.魔法注释
ES2020提案 引入
import()函数,支持动态加载模块。使用 /* webpackChunkName:'名字' */ 可以将代码打包到一个 js 文件中
function run() {
// 将公共组件 sort 和 a 打包到一个文件中
import(/* webpackChunkName:'sort' */ './a.js').then(
// 解构赋值获取 default 内容
({ default: a }) => {
console.log(a.a, 'default')
a.name()
}
)
import(/* webpackChunkName:'sort' */ './sort.js').then(
({ default: sort }) => {
console.log(sort)
console.log(sort([458, 12, 9, 4788, 231, 4, 1, 6, 9]))
}
)
}
run()
对 output 配置,上面的导入文件会被打包到指定目录中
output: {
filename: '[name].bundle.js', // 导出文件 根据 entry 属性文件名适配
path: path.resolve(__dirname, 'dist'),
},
上面两个import 文件将会被打包到 ./dist/sort.bundle.js 文件中
4.样式文件单独打包
1. CSS 样式单独打包
安装 MiniCssExtractPlugin
$yarn mini-css-extract-plugin --dev
webpack.config.js 配置
//单独打包 css 模块
const webpack = require('webpack')
// 导入配置模块
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/main.js',
output: {
filename: 'bundle.js', // 导出文件 根据 entry 属性文件名适配
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 'style-loader', //使用样式单独打包后,这里不在使用 style loader 而是使用 miniCss 处理
MiniCssExtractPlugin.loader, //通过命令行标签方式注入
'css-loader',
],
},
],
},
plugins: [
new MiniCssExtractPlugin(), //自动提取 css 文件到单独文件中
],
}
css 文件将会被单独打包成单个文件,推荐 150 k以上进行打包优化。小于150k可能有负优化
2. 压缩 css 样式文件体积优化
通过压缩优化,减小 css 样式文件体积
const webpack = require('webpack')
// css 单独打包 plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// css 代码压缩
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const path = require('path')
module.exports = {
plugins: [
new MiniCssExtractPlugin(), //自动提取 css 文件到单独文件中
],
module: {
rules: [
{
test: /\.css$/i,
use: [
// 'style-loader', //使用样式单独打包后,这里不在使用 style loader 而是使用 miniCss 处理
MiniCssExtractPlugin.loader, //通过命令行标签方式注入
'css-loader',
],
},
],
},
optimization: {
minimizer: [
new OptimizeCssAssetsWebpackPlugin(), //css 代码压缩
],
},
}
导出的 css 文件将被压缩
3.配置 minimizer 导致 js 压缩失效问题
上面配置 minimizer 压缩 css 文件后 , webpack会认为用户想自定义压缩数组导致 js 压缩失效,所以需要手动添加 js 代码压缩
// 引入 js 代码压缩插件
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
//...
optimization: {
// minimize: true, //js 代码压缩
minimizer: [
new OptimizeCssAssetsWebpackPlugin(), //css 代码压缩
new TerserWebpackPlugin(), //手动添加 js代码压缩
],
},
//...
}