https://webpack.docschina.org/
一、Webpack快速上手
yarn init --yes 初始化package.json
yarn add webpack webpack-cli --dev 安装webpack 和cli模块
yarn webpack --version 查看版本
yarn webpack 打包
二、ebpack 配置文件
会按照约定打包
src/index ----> dist/main.js
在根目录下添加文件webpack.config.js
const path = require('path')
module.exports = {
entry:'./src/main.js', //指定webpack打包入口文件路径
output:{
filename:'dist111.js', //打包输出文件的名称
path:path.join(__dirname,'output'), //打包输出文件路径
}
}
三、Webpack 工作模式
// webpack.development.config.js
module.exports = {
mode: 'development',
};
// webpack.production.config.js
module.exports = {
mode: 'production',
};
// webpack.custom.config.js
module.exports = {
mode: 'none',
};
如果要根据 webpack.config.js 中的 mode 变量更改打包行为,则必须将配置导出为函数,而不是导出对象:
var config = {
entry: './app.js',
//...
};
module.exports = (env, argv) => {
if (argv.mode === 'development') {
config.devtool = 'source-map';
}
if (argv.mode === 'production') {
//...
}
return config;
};
四、Webpack 资源模块加载
Webpack默认只会解析js代码
安装css-loader
yarn add css-loader --dev
webpack.config.js
module.exports={
module:{
rules:[
{
test:'/.css$/',
use:'style-loader',//先执行css-loader,倒序执行
use:'css-loader'
}
]
}
}
安装style-loader
yarn add style-loader --dev
作用:将css-loader转换的结果通过style标签的形式追加到页面上
Loader是Webpack的核心特性,借助于Loader就可以加载任何类型的资源
五、webpack 导入资源模块
JavaScript驱动整个前端应用
import './heading.css'
六、webpack 文件资源加载器Loader
yarn add file-loader --dev
webpack.config.js
output:{
publicPath:'dist/'
},
module:{
rules:[
{
test:/.css$/,
use:[
'style-loader',
'css-loader',
]
},{
test:/.png$/,
use:'file-loader'
}
]
}
七、webpack URL 加载器 url-Loader
安装url-loader
yarn add url-loader --dev
module:{
rules:[
{
test:/.css$/,
use:[
'style-loader',
'css-loader',
]
},{
test:/.png$/,
use:'url-loader'
}
]
}
yarn webpack 打包测试
小文件使用Data URLs,减少请求次数
大文件单独提取存放,提高加载速度
module:{
rules:[
{
test:/.css$/,
use:[
'style-loader',
'css-loader',
]
},{
test:/.png$/,
use:{
loader:'url-loader',
options:{
limit:10 * 1024, //限制10K一下的用url-loader 转换
}
}
}
]
}
超出10KB文件单独提取存放,小于10KB文件转换为Data URLs 嵌入代码,需要同时安装file-loader,url-loader对于超出的文件会去调用file-loader模块
八、Webpack 常用加载器分类
- 编译转换类型 :加载到的资源模块转换为js代码,例如css-loader
- 文件操作类型:加载到的资源模块拷贝到输出目录,同时将文件访问路径向外导出,例如file-loader
- 代码检查类:对于加载到的资源文件进行校验的加载器,目的是为了统一代码风格 例如eslint-loader
九、Webpack 与ES2015
因为模块打包需要,所以处理import和export,并不能够转换代码中其他的es6特性,要转换es6,const等语法,需要安装babel-loader,而babel-loader 需要依赖babel的核心模块,所以需要同时安装@babel/core和用于转换具体特性转换插件的集合 @babel/preset-env
yarn add babel-loader @babel/core @babel/preset-env --dev
webpack.config.js
module:{
rules:[
{
test:/.js$/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
}
]
}
再次运行yarn webpack
Webpack 只是打包工具,加载器可以用力编译转换代码
十、Webpack加载资源的方式
- 一、遵循ES Modules 标准的import声明
- 二、遵循CommonJS标准的require函数
- 三、遵循AMD标准的define函数和require函数
还有一些独立的加载器在工作的时候也会去处理加载的资源当中导入的模块,例如css-loader 加载的文件,@import指令和url函数也会触发相应的资源模块的加载,html-loader 加载的html文件当中的图片标签的src属性也会触发相应的模块加载。
安装html-loader
yarn add html-loader --dev
webpack.config.js
module:{
rules:[
{
test:/.html$/,
use:{
loader:'html-loader',
options:{
attrs:['img:src','a:href']
}
}
}
]
}
- 四、*样式代码中的@import指令和url函数
- 五、 * HTML代码中图片标签的src属性
十一、Webpack 核心工作原理
根据配置找到入口文件作为打包的入口,一般为js文件,根据代码中的import或者require 来解析推断文件依赖的模块,分别去解析每一个资源模块对应的依赖,就会形成整个项目中用到的所有文件的依赖关系的依赖树 ,webpack会递归这个依赖树,找到每个节点对应的资源文件,最后根据配置文件中的rules属性找到这个模块对应的加载器,然后加载这个模块,最后会将加载到的结果放到打包结果文件当中,从而去实现整个项目的打包,Loader 机制是Webpack 的核心
十二、Webpack Loader的工作原理(开发一个Loader )
yarn add marked --dev
// markdown-loader.js
const marked = require('marked')
module.exports = source =>{
// return 'console.log("hello")';//必须是js
const html = marked(source)
// return `module.exports = ${JSON.stringify(html)}`;//方法1
// return `exports default = ${JSON.stringify(html)}`;//方法2
// 返回html 字符串交给下一个loader处理
return html
}
// webpack.config.js
module:{
rules:[
{
test:/.md$/,
use:[
'html-loader',
'./markdown-loader.js'
]
}
]
}
Loader 负责资源文件从输入到输出的转换,loader 是一个管道的概念,对于同一个资源可以依次使用多个Loader,例如css-loader---->style-loader等
十三、Webpack 插件机制介绍
插件目的是为了增强Webpack 自动化能力,Loader是实现资源模块的加载从而实现整体项目的打包,而Plugin解决其他的自动化工作,例如在打包之前plugin可以帮我们清除dist目录,可以用来拷贝静态文件至输出目录,压缩输出的代码。webpack + plugin 实现了大多数前端工程化工作。
webpack != 前端工程化
十四、Webpack 常用插件--自动清除输出目录
yarn add clean-webpack-plugin --dev
webpack.config.js
const {cleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
plugins:[
new cleanWebpackPlugin()
]
}
再次运行yarn webpack
十五、Webpack 自动生成HTML插件
yarn add html-webpack-plugin --dev
webpack.config.js
const {htmlWebpackPlugin} = require('html-webpack-plugin')
module.exports = {
output:{
//publicPath:'dist/' 这里需要注释掉这个路径
},
plugins:[
//用于生成index.html文件
new htmlWebpackPlugin({
title:'设置标题',
meta:{
viewport:'width=device-width'
},
template:'./src/index.html',//指定模板文件
}),
// 用于生成about.html文件
new htmlWebpackPlugin({
filename:'about.html'
}),
]
}
再次运行yarn webpack
十六、Webpack 常用插件 使用总结
yarn add copy-webpack-plugin --dev
webpack.config.js
const {copyWebpackPlugin} = require('copy-webpack-plugin')
module.exports = {
plugins:[
new copyWebpackPlugin([
'plublic'
]),
]
}
再次运行yarn webpack
十七、Webpack 开发一个插件
相比于Loader,Plugin拥有更宽的能力范围,plugin通过钩子机制实现。
webpack 要求必须是一个函数或者一个包含apply方法的对象
class MyPlugin {
apply(compiler) {
console.log("MyPlugin");
compiler.hook.emit.tap("MyPlugin", (compiler) => {
// 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: () => widthoutComments.length,
};
}
}
});
}
}
module.exports = {
plugin: [new MyPlugin()],
};
十八、Webpack 自动编译
yarn webpack --watch
十九、Webpack 自动刷新浏览器
browser-sync dist --files "**/*"
二十、Webpack Dev Server
集成【自动编译】和【自动刷新浏览器】等功能
yarn add webpack-dev-server --dev
yarn webpack-dev-server --open 运行并且自动打开浏览器
二十一、Webpack Dev Server静态资源访问
contentBase 可以为webpack dev Server 额外的为开发服务器指定查找资源目录
webpack.config.js
module.exports = {
devServer:{
contentBase:['./plublic']
}
};
二十二、Webpack Dev Server 代理API
webpack.config.js
module.exports = {
devServer:{
contentBase:['./plublic'],
proxy:{
'/api':{
target:'https://api.github.com',
pathRewrite:{
'^/api':''
},
//不能使用localhost:8080作为请求github的主机名
changeOrigin:true
}
}
}
};
main.js
const ul = ducument.createElement('ul');
document.body.append(ul)
//跨域请求,虽然GitHub支持CORS,但不是每个服务端都应该支持
//fetch('https://api.github.com/users')
fetch('/api/users')
.then(res=>res.json())
.then(data=>{
data.forEach(item=>{
const li = document.createElement('li')
li.textContent = item.login
ul.append(li)
})
})
二十三、Webpack Source Map 配置 (源代码地图)
webpack.config.js
module.exports = {
devtool:'source-map'
};
二十四、Webpack eval模式的Source Map
webpack.config.js
module.exports = {
devtool:'eval'
};
可以定位错误文件,不能定位具体行列信息
二十五、Webpack devtool 模式对比
- eval 可以定位错误文件,不能定位具体行列信息,不生成.map文件
- eval-source-map 可以定位错误出现的文件行和列信息,生成.map文件
- cheap-eval-source-map 指定位到行 没有列的信息,生成.map文件,代码为转换es6过后的代码
- cheap-module-eval-source-map 只定位到行 代码为源代码
- eval - 是否使用eval执行模块代码
- cheap - Source Map 是否包含行信息
- module - 是否能够得到Loader 处理之前的源代码
二十六、Webpack 选择Source Map 模式
开发环境:cheap-moudule-eval-source-map
- 每行代码不超过80个字符
- 代码经过Loader 转换后的差异较大
- 首次打包速度慢无所谓,重写打包相对较快
生产环境:none
- Source Map 会暴露源代码
- 调试是开发阶段的事情
或者nosources-source-map:可以找到源代码错误位置,不至于暴露源码。
理解不同模式的差异,适配不同的环境
二十七、Webpack 自动刷新的问题 开启HMR 模块热更新
- 需求:自动刷新导致的页面状态丢失,在页面不刷新的前提下模块也可以及时更新
HMR是Webpack中最强大的功能之一,极大程度的提高了开发者的工作效率。
-Webpack 开启HMR
HMR集成在webpack-dev-server中,不需要单独安装模块
yarn webpack-dev-server --hot 开启热更新
也可以在配置文件中配置开启热更新
const webpack = require('webpack')
module.exports ={
devServer:{
hot:true
},
plugins:[
new webpack.HotModuleReplacementPlugin()
]
}
yarn webpack-dev-server --open
二十八、Webpack HMR 的疑问
- webpack 中的HMR并不可以开箱即用
Webpack 中的HMR需要手动处理模块热替换逻辑
现有的脚手架工具中已经包括了HMR逻辑
二十九、Webpack 使用HMR API
main.js
module.hot.accept('./editor',()=>{
console.log('editor 模块更新了');
})
三十、Webpack HMR注意事项
- 处理HMR的代码报错会导致自动刷新
hotOnly 不会使用自动刷新
devServer:{
hotOnly:true
}
- 没有启用HMR的情况下,HMR API报错
先去判断
if(module.hot){
...
...
}
- 代码找那个多了一些与业务无关的代码
代码压缩后会自动去掉
三十一、Webpack 不同环境下的配置
- 1、配置文件根据环境不同导出不同的配置
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: {
rulesL: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(png|jpg?g|gif)$/,
use: {
loader: "file-loader",
options: {
outputPath: "img",
name: "[name].[ext]",
},
},
},
],
},
plugin: [
new HtmlWebpackPlugin({
title: "webpack title",
template: "./src/index.html",
}),
new webpack.HotModuleReplacementPlugin(),
],
};
if (env === "production") {
config.mode = "production";
config.devtool = false;
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(["plublic"]),
];
}
return config
};
// yarn webpack --env production
- 2、一个环境一个配置文件
三十二、Webpack 不同环境的配置文件
webpack.common.js 公共的配置
webpack.dev.js 开发环境
webpack.prod.js 生产环境
安装yarn add webpack-merge --dev
例如webpack.prod.js
const common = require("./webpack.common");
const merge = require("webpack-merge");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = merge(common, {
mode: "production",
plugin: [new CleanWebpackPlugin(), new CopyWebpackPlugin(["plublic"])],
});
yarn webpack --config webpack.prod.js //指定运行文件
也可以将命令构建到npm 模块
package.json -->scripts-->'build':'webpack --config webpack.prod.js'
运行yarn build
三十三、Webpack DefinePlugin
为我们的代码注入全局成员 process.env.NODE_ENV
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("https://api.example.com"),
}),
],
};
三十四、Webpack 使用Tree Shaking
Tree Shaking 不是指某个配置选项,是一组功能搭配使用后的优化效果,会在生产模式下(production)自动启用
module.exports = {
optimization: {
usedExports: true, //用来标记冗余代码
minimize:true, 用于清除标记的冗余代码
},
}
yarn webpack
三十五、Webpack 合并模块 concatenateModules
concatenateModules :尽可能的将所有的模块合并输出到一个函数中,即提升了运行效率,又减少了代码的体积。Scope Hoisting 作用域提升
三十六、Webpack Tree Shaking 与Babel
Tree Shaking 前提是ES Modules,由Webpack打包的代码必须使用ESM,为了转换代码中的ECMAScript新特性,会选择babel-loader处理js,ES Modules可能会转换成CommonJS,
在最新版本的babel-loader中自动帮我们关闭了ES M的转换插件。
三十七、Webpack sideEffects
标识代码是否有副作用,为Tree Shaking 提供更大的空间
副作用:模块执行时除了导出成员之外所做的事情
sideEffects 一般用于npm 包标记是否有副作用
optimization: {
//开启功能
sideEffects:true,
// 模块指导处被使用的成员
// usedExports: true,
// 尽可能合并每一个模块到一个函数中
// concatenateModules:true,
// 压缩输出结果
// minimize:true
},
package.json
"sideEffects":false, //标记没有副作用
yarn webpack
三十八、Webpack sideEffects 注意
确保代码没有副作用
例如为Number的原型添加一个扩展方法 就是副作用的代码
Number.prototype.pad = function(size){
//将数字转换为字符 1 2
let result = this + ''
//在数字前补指定个数的0 001 002
while(result.length < size ){
result = '0' + result
}
return result
}
在代码中载入的css 模块也属于副作用代码,解决办法 在package.json 中关掉副作用,或者标记当前文件那些是有副作用的,
package.json
有副作用的文件
"sideEffects":[
'./src/extend.js',
'*.css'
]
yarn webpack
三十九、Webpack 代码分割 Code Splitting
代码分割的两种而方式
- 一、Webpack 多入口打包
const webpack = require("webpack");
const {CleanWebpackPlugin} = require('Clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: "none",
// entry: "./src/index.js",
entry:{//多入口
index:'./src/index.js',
main:'./src/main.js'
},
output: {//多输出
filename: "[name].bundle.js",
},
module:{
rules:[
{
test:'/\.css$/',
use:[
'style-loader',
'css-loader'
]
}
]
},
optimization: {
// sideEffects:true,
// 模块指导处被使用的成员
// usedExports: true,
// 尽可能合并每一个模块到一个函数中
// concatenateModules:true,
// 压缩输出结果
// minimize:true
},
plugins: [
new webpack.DefinePlugin({
API_BASE_URL: JSON.stringify("https://api.example.com"),
}),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:'indexhtml',
template:'./src/index.html',
filename:'index.html',
chunks:['index'],//指定需要引入的bundle
}),
new HtmlWebpackPlugin({
title:'main.html',
template:'./src/main.html',
filename:'main.html',
chunks:['main']//指定需要引入的bundle
})
],
};
Webpack 提取公共模块
optimization: {
splitChunks:{
chunks:'all',
}
}
- 二、Webpack 动态导入
// import post from './post/post'
// import album from './album/album'
const render = () => {
const hash = window.location.hash || "#posts";
const mainElement = document.querySelector("main");
mainElement.innerHTML = "";
if (hash === "#posts") {
import("./posts/posts").then(({ default: posts }) => {
mainElement.appendChild(posts());
});
} else if (hash === "#album") {
import("./album/album").then(({ default: posts }) => {
mainElement.appendChild(album());
});
}
};
render();
window.addEventListener("hashchange", render);
四十、Webpack 魔法注释Magic Comments
if (hash === "#posts") {
import(/*webpackChunkName:'posts'*/"./posts/posts").then(({ default: posts }) => {
mainElement.appendChild(posts());
});
} else if (hash === "#album") {
import((/*webpackChunkName:'album'*/"./album/album").then(({ default: posts }) => {
mainElement.appendChild(album());
});
}
四十一、Webpack MiniCssExtractPlugin 提取CSS到单文件
yarn add mini-css-extract-plugin --dev
四十二、Webpack OptimizeCssAssetsWebpackPlugin 压缩CSS插件
yarn add optimize-css-assets-webpack-plugin --dev
yarn add terser-webpack-plugin --dev
webpack.config.js
optimization: {
minimizer: [
new TerserWebpackPlugin(),
new OptimizeCssAssetsWebpackPlugin(),
],
}
四十三、Webpack 输出文件名Hash
output: {
//多输出
filename: "[name]-[contenthash:8].bundle.js",
path: path.resolve(__dirname, "./dist"),
},
可以指定位数,当前为8 不指定不写 :8