小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
快速上手
- 安装
yarn add webpack webpack-cli --dev
- 打包
yarn webpack
Webpack 配置文件
// webpack.config.js
const path = require("path")
module.exports = {
entry: "./src/main.js",//入口文件
output:{
filename:"bundle.js", //生成的 文件名
path:path.join(__dirname,"output") //输出的文件目录 必须是绝对路径
} //输出文件
}
webpack工作模式
const path = require("path")
module.exports = {
mode: "none", //工作模式 development production none
entry: "./src/main.js",//入口文件
output:{
filename:"bundle.js", //生成的 文件名
path:path.join(__dirname,"dist") //输出的文件目录 必须是绝对路径
} ,//输出文件
}
Webpack常用加载器(loader)
const path = require("path")
module.exports = {
mode: "none", //工作模式 development production none
entry: "./src/main.js",//入口文件
output:{
filename:"bundle.js", //生成的 文件名
path:path.join(__dirname,"dist"), //输出的文件目录 必须是绝对路径
publicPath:"dist/"
} ,//输出文件
module: {
rules: [
{
test:/.css$/,
use: [
"style-loader",
"css-loader"
]
},
// {
// test:/.png$/,
// use:"file-loader"
// }, //较大文件,单独提取存放,提高加载速度
{
test:/.png$/,
use: {
loader: "url-loader",
options: {
limit: 10 * 1024 //10KB 限制大小之后超过限制的图片会默认调取file-loader所以也需要安装 file-loader
}
}
}//较小文件使用,减少请求次数
]
}
}
- 编译转换类
- css-loader
- 文件加载类型
- file-loader
- 代码检查类
- eslint-loader
Webpack与ES2015
- Webpack只是打包工具不会编译ES6的代码可以通过 加载器来实现
- 加载器可以用来编译转换代码
//babel-loader
{
test:/.js$/,
use: {
loader: "babel-loader",
options:{
presets: ["@babel/preset-env"]
}
}
},
Webpack 模块加载方式
- 遵循ES Modules标准的import声明
- 遵循CommonJS标准的require函数
- 遵循AMD标准的define函数和requir函数
- 部分Loader加载的资源中一些用法也会触发资源模块加载
- @import url()
- background-image:url()
- html中的src
Webpack开发一个Loader
markdown-loader
const marked = require("marked") //解析markdown语法
module.exports = source => {
// console.log(source);
// return "console.log('hello---')"
const html = marked (source)
// return `module.ecports = ${JSON.stringify(html)}`
// return `export default ${JSON.stringify(html)}`
// 返回html 字符串交给下一个loader处理
return html
}
{
test:/.md$/,
use:["html-loader","./markdown-loader.js"] //按照从后往前的处理
},
loder负责资源文件从输入到输出的转换,对于同一个资源可以依次使用多个loader
Webpack插件机制
- 增强Webpack自动化能力
- eg. 清除dist目录
- eg. 拷贝静态文件至输出目录
- eg. 压缩输出代码
const path = require("path")
const { CleanWebpackPlugin } = require("clean-webpack-plugin") //清理dist目录
const HtmlWebpackPlugin = require("html-webpack-plugin")//自动生成html插件
const CopyWebpackPlugin = require("copy-webpack-plugin")
module.exports = {
mode: "none", //工作模式 development production 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.js"]
},
{
test:/.js$/,
use: {
loader: "babel-loader",
options:{
presets: ["@babel/preset-env"]
}
}
},
{
test:/.css$/,
use: [
"style-loader",
"css-loader"
]
},
// {
// test:/.png$/,
// use:"file-loader"
// }, //较大文件,单独提取存放,提高加载速度
{
test:/.png$/,
use: {
loader: "url-loader",
options: {
limit: 10 * 1024 //10KB 限制大小之后超过限制的图片会默认调取file-loader所以也需要安装 file-loader
}
}
}//较小文件使用,减少请求次数
]
},
plugins:[
// 用于清除dist目录
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"
// }),
// 用于复制文件到dist
new CopyWebpackPlugin({
patterns:[
{
from:"public", // "public/**" 可以是目录 也可以使用通配符
to :"public"
}
]
})
]
}
相比于Loader,plugin拥有更宽的能力范围,plugin通过钩子机制实现 要求插件是一个函数或者是一个包含apply方法的对象
Webpack 开发一个插件
const path = require("path")
const { CleanWebpackPlugin } = require("clean-webpack-plugin") //清理dist目录
const HtmlWebpackPlugin = require("html-webpack-plugin")//自动生成html插件
const CopyWebpackPlugin = require("copy-webpack-plugin")
class MyPlugin {
apply(compiler){
console.log("MyPlugin");
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
}
}
}
})
}
}
module.exports = {
mode: "none", //工作模式 development production 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.js"]
},
{
test:/.js$/,
use: {
loader: "babel-loader",
options:{
presets: ["@babel/preset-env"]
}
}
},
{
test:/.css$/,
use: [
"style-loader",
"css-loader"
]
},
// {
// test:/.png$/,
// use:"file-loader"
// }, //较大文件,单独提取存放,提高加载速度
{
test:/.png$/,
use: {
loader: "url-loader",
options: {
limit: 10 * 1024 //10KB 限制大小之后超过限制的图片会默认调取file-loader所以也需要安装 file-loader
}
}
}//较小文件使用,减少请求次数
]
},
plugins:[
// 用于清除dist目录
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"
// }),
// 用于复制文件到dist
new CopyWebpackPlugin({
patterns:[
{
from:"public", // "public/**" 可以是目录 也可以使用通配符
to :"public"
}
]
}),
new MyPlugin()
]
}
通过在生命周期的钩子中挂载函数实现扩展
Webpack开发体验问题
- Webpack自动编译
yarn webpack --watch
- Webpack编译过后 自动编译 自动刷新浏览器 Webpack Dev Server
yarn webpack-dev-serve --open
// 相关配置
devServer: {
contentBase:"./public",
proxy: {
"/api" : {
target:"https://api.github.com", //http://localhost:8080/api/users -> https://api.github.com/api/users 将以api开头的接口 替换掉
pathRewrite:{
"^/api":"" //http://localhost:8080/api/users -> https://api.github.com/users 使用正则 替换
},
changeOrigin: true //不能使用localhost:8080作为请求 github的主机名
}
}
},
Source Map
源代码地图
- 解决了源代码与运行代码不一致所产生的问题
Webpack 配置 Source Map
devtool:"source-map",
Webpack 配置 Source Map eval模式下的Source Map
速度最快 定位只能定位到文件不能定位到行列信息 eval 是否使用eval执行模块代码 cheap-Source Map 是否包含行信息 module 是否能够得到Loader处理之前的源代码
选择合适的Source Map
- 开发模式 cheap-moudle-eval-source-map
- 生产模式 none 不生成Source Map 会暴露源代码 或者 nosources-source-map
Webpack自动刷新的问题 HMR 介绍
页面不刷新的前提下,模块也可以及时更新 模块热替换
hot: true, //devServer 开启热更新
处理JS模块热替换
js的热替换需要手动对应 不同的模块去单独的处理
// 热替换处理函数
module.hot.accept("模块文件名", () => {
// 手动处理
})
处理图片的模块的热替换
// 热替换处理函数
module.hot.accept("模块文件名", () => {
// 手动处理
img.src = background
})
HMR注意事项
- 处理HMR的代码报错会导致自动刷新 配置中 修改为 hotOnly 为 true hot 为 true时 当热替换失败时会自动刷新页面
- 没启用HMR的情况下,HMRAPI报错 //使用时 先判断 module.hot是否存在
- 在代码中写了很多与业务功能无关的代码
不同环境下的配置
- 配置文件根据环境不同导出不同的配置 导出一个函数去判断
// yarn webpack --env production
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = (env, argv) => {
const config = {
mode: 'development',
entry: './src/main.js',
output: {
filename: 'js/bundle.js'
},
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
outputPath: 'img',
name: '[name].[ext]'
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Tutorial',
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin()
]
}
if (env === 'production') {
config.mode = 'production'
config.devtool = false
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
}
return config
}
- 一个环境对应一个配置文件
// webpack.common.js yarn webpack --config webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/main.js',
output: {
filename: 'js/bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
outputPath: 'img',
name: '[name].[ext]'
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Tutorial',
template: './src/index.html'
})
]
}
// webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
// webpack.prod.js
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'])
]
})
Webpack DefinePlugin
注入变量
new webpack.DefinePlugin({
API_BASE_URL:"'https://api.example.com'" //传入 的是JS的代码片段
})
webpack 体验 Tree Shaking
前提 由webpack打包的代码必须使用ESM 摇掉代码中为引用的部分 未引用代码(dead-code) 去掉冗余的代码
// 新版的babel-loader 不会失效 如果要确保 可以正常使用 tree shaking 可以配置
{
test:/.js$/,
use: {
loader: "babel-loader",
options:{
presets: [
["@babel/preset-env",{ modules:false }] //确保是ESM方式 保证 tree shaking 能够正常运行
]
}
}
},
optimization: {
usedExports: true, //在输出结果中只导出外部使用了的成员 负责 标记
minimize:true //未引用的代码 将会移除掉 负责去除
}
webpack 合并模块
optimization: {
usedExports: true, //在输出结果中只导出外部使用了的成员 负责 标记
minimize:true, //未引用的代码 将会移除掉 负责去除
concatenateModules:true //极可能的将所有的模块合并输出到一个函数中 提升运行效率,减少了代码的体积
}
webpack sideEffects 副作用
模块执行时除了导出成员之外所作的事情 一般用于npm包标记是否有副作用
optimization: {
sideEffects:true, //开启这个功能 开启后 会检查当前代码所属的package.json中 有没有标识 假如有标识 没有用到的模块 就不会被打包
}
// package.json
"sideEffects": false //标识代码没有副作用
"sideEffects": [
"./src/*.css"
] //写成数组将会忽略其中的路径
webpack 代码分割/分包 Code Splitting
- 多入口打包 常应用于多页面程序 一个页面对应一个打包入口 公共部分单独提取
entry:{
index:"./src/index.js",
aldum:"./src/aldum.js"
}
output:{
filename: "[name].bundle.js"
}
new HtmlWebpackPlugin({
title: "Webpack Plugin Sample",
meta: {
viewport: "width-device-width"
},
filename:"index.html"
template: "./src/index.html"
chunks:['index']
}),
new HtmlWebpackPlugin({
title: "Webpack Plugin Sample",
meta: {
viewport: "width-device-width"
},
filename:"aldum.html"
template: "./src/aldum.html"
chunks:['aldum']
}),
- 提取公共模块 不同入口中肯定会有公共部分模块
optimization: {
splitChunks: {
chunks: "all"
}
}
- 动态导入 动态导入的模块会被自动分包
import("./posts/posts").then(({ default: posts }) => { //结构出default
处理程序
})
webpack 魔法注释
动态导入的模块会被自动分包 魔法注释可以来命名 包名
/* webpackChunkName: "album" */
//假如设置的 打包名称相同 会打包到 同一文件夹
webpack MiniCssExtractPlugin
提取css到单个文件 css 样式超过 155kb 后 提取比较好
// yarn add mini-css-extract-plugin --dev
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module:{
rules:[
{
test:/.css$/,
use: [
// "style-loader", 将样式通过style标签注入
MiniCssExtractPlugin.loader,
"css-loader"
]
}
]
},
plugins: {
new MiniCssExtractPlugin()
}
webpack OptimizeCssAssetsWebpackPlugin 压缩输出的css文件
// yarn add optimize-css-assets-webpack-plugin --dev
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin")
plugins: {
new OptimizeCssAssetsWebpackPlugin()
}
// 应该 放在minimizer 可由 minimize 来开启
// yarn add terser-webpack-plugin --dev
const TerserWebpackPlugin = require("terser-webpack-plugin")
optimization: {
usedExports: true, //在输出结果中只导出外部使用了的成员 负责 标记
minimizer:[ //压缩代码的 插件 放在这里 在 minimize 为 true时 开启
new OptimizeCssAssetsWebpackPlugin(),
new TerserWebpackPlugin()
]
minimize:true, //未引用的代码 压缩代码时 将会移除掉 负责去除
concatenateModules:true //极可能的将所有的模块合并输出到一个函数中 提升运行效率,减少了代码的体积
}
webpack 输出文件名 Hash
生产模式下,文件名使用Hash
// chunkhash 同一个chunk的hash文件名才会改变
output:{
filename:"[name]-[chunkhash].bundle.js", //生成的 文件名
path:path.join(__dirname,"dist"), //输出的文件目录 必须是绝对路径
// publicPath:"dist/"
}
plugins: {
new OptimizeCssAssetsWebpackPlugin({
filename:"[name]-[chunkhash].bundle.css"
})
}
// hash 一个文件改动 所有的文件名 都会改变
output:{
filename:"[name]-[hash].bundle.js", //生成的 文件名
path:path.join(__dirname,"dist"), //输出的文件目录 必须是绝对路径
// publicPath:"dist/"
}
plugins: {
new OptimizeCssAssetsWebpackPlugin({
filename:"[name]-[hash].bundle.css"
})
}
// contenthash 文件级别的hash 文件内容发生变化 更改hash[contenthash:8] 表示长度 默认是20位
output:{
filename:"[name]-[contenthash].bundle.js", //生成的 文件名
path:path.join(__dirname,"dist"), //输出的文件目录 必须是绝对路径
// publicPath:"dist/"
}
plugins: {
new OptimizeCssAssetsWebpackPlugin({
filename:"[name]-[contenthash].bundle.css"
})
}