金石有声,不拷不鸣
引言
在webpack之类的构建工具诞生之前,前端项目构建一直处于"高耕火种"时代,项目代码压缩混淆、 js/css等依赖的合并都需要手动操作。webpack的出现解决了前端项目构建的很多痛点,可以说是前端工程化中必学必会的一环。
什么是Webpack?
Webpack 是前端项目的构建工具,它可以解析项目的依赖(js/css/图片资源等),对它们进行打包处理。
Webpack主要功能:
- 代码转译
- 模块合并
- 混淆压缩
- 代码分割
- 代码刷新
- 自动刷新
- 自动部署
webpack环境搭建
webpack基于Node环境,Node请自行下载
Webpack安装
全局安装: npm install webpack webpack-cli -g
局部安装(推荐): npm install webpack webpack-cli -D
搭建Demo
- 新建basic文件夹并用Vscode打开
- 局部安装 webpack webpack-cli
npm install webpack webpack-cli -D
Webpack-cli
Webpack-cli之前是再webpack包中的,webpack 4.0以后作为单独的模块进行管理。
执行Webpack
Webpack 通过指令 webpack
来对项目进行打包,但是这个指令只在全局安装的Webpack有用。npm5.2 提供了 npx
指令,npx
可以在项目中直接执行模块的指令。
终端执行npx webpack
webpack执行了,但是依赖和配置有问题,打包的入口、出口文件等都需要配置。
新建默认入口文件
webpack默认入口文件为: 根目录/src/index.js
-
新建 根目录/src/index.js
// /src/index.js const init = () => { console.log("webpack真好玩") } init()
-
执行
npx webpack
,打包成功生成 /dist/main.js
应用场景
在真实开发中会涉及多个模块依赖的场景
-
新建 src/a.js
// moduleA console.log("我是模块A"); module.exports = { name: "moduleA" }
-
src/index.js 导入 a.js 并重新构建
const a = require("./a") console.log(a.name); const init = () => { console.log("webpack真好玩") } init()
-
安装vscode插件 - run code ,并执行 main.js
在浏览器端执行
前面的 require
、module.exports
属于 CommonJS模块化规范 ,浏览器默认不支持该语法。但是代码经过webpack打包后,会自动转换模块化规范。
-
新建 index.html (src/index.html),引入 src/index.js
<!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.0"> <title>Document</title> </head> <body> <!-- 使用CommonJS模块化规范 --> <script src="./index.js"></script> </body> </html>
-
引入 dist/main.js
<body> <!-- 使用CommonJS模块化规范 --> <!-- <script src="./src/index.js"></script> --> <!-- 引入webpack打包后的js --> <script src="./dist/main.js"></script> </body>
Webpack配置
webpack四大核心概念:
- 入口(entry):程序入口的js
- 出口(output):打包之后存放的位置
- loader:对项目依赖、文件进行转换处理
- plugin:可以处理loader做不了的工作
Webpack配置文件
Webpack配置文件名默认为 webpack.config.js,配置文件必须遵守 CommmonJS 模块化规范。
const path = require("path")
// webpack配置文件必须遵循CommonJS规范
module.exports = {
// 项目打包入口文件
entry: "./src/index.js",
// 项目出口配置
output: {
// path.resolve("path")/path.join(_dirname,"path"):将相对路径解析为绝对路径
path: path.resolve("./dist/"),
// 打包后的文件名
filename: 'bundle.js'
},
// 打包模式(默认:production):production || development
mode: "development"
}
webpack指令参数
-
--config 文件名:
使用自定义配置文件执行打包指令:
npx webpack --config webpack.custom.config.js
可以根据开发 / 上线 不同环境使用不同的配置文件
-
新增script指令:
{ "scripts": { // 终端输入 npm run build,使用 webpack.custom.config.js进行打包 "build": "npx webpack --config webpack.custom.config.js" } }
html插件
webpack每次编译打包不会将index.html打包进来,通过html插件可以打包html文件同时自动引入其他css/js等依赖
html插件作用
1、打包后自动复制一份目标html文件
2、html自动引入打包后的js(注意:不要手动引入js!!!)
-
安装:
npm i html-webpack-plugin -D
-
在webpack配置文件中配置插件
// webpack.config.js const HtmlPlugin = require("html-webpack-plugin") module.exports = { mode: "production", // 配置插件字段 plugins: [ // 新建一个HtmlPlugin实例 new HtmlPlugin( { // 打包输出文件名 filename: "index.html", // 被复制的目标html template: "./src/index.html" } ) ] }
-
修改index.html
<body> <!-- 使用CommonJS模块化规范 --> <!-- <script src="./src/index.js"></script> --> <!--新增节点--> <ul> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> </ul> <!--使用html-webpack-plugin无需引入js,它会自动帮你引入--> <!--<script src="./src/index.js"></script>--> </body>
-
执行指令
npx webpack
进行打包
自动编译工具
每次修改代码后,都要执行npm run build
重新编译,我们可以借助插件或工具让webpack进行自动编译
- watch mode
- webpack-dev-server(最推荐)
- webpack-dev-middleware
watch mode
webpack 开启watch模式,会监视项目文件的变化,当发现有修改的代码时会自动编译打包,输出文件。
方式一:webpack-cli打包指令增加--wtach
参数
1、package.json 新增 watch 脚本
"scripts": {
"build": "npx webpack --config webpack.custom.config.js",
"watch": "npx webpack --config webpack.config.js --watch"
}
2、终端执行 npm run watch
,开始监听项目文件
方式二:webpack配置文件开启watch模式
// webpack.config.js
module.exports = {
// 打包模式(默认:production):production || development
mode: "production",
// 开启watch模式
watch: true
}
webpack-dev-server
这个npm包可以在本地开启服务,在内存中生成打包的bundle.js.打包效率高在修改代码后重新构建并刷新页面。注意:使用webpack-dev-server 必须先安装webpack
- 1、安装 webpack-dev-server :
npm i webpack-dev-server -D
- 2、 webpack-dev-server 指令:
npx webpack-dev-server --port 3001
- --port:指定服务端口号
- --hot:开启热更新
- --open:是否自动打开浏览器访问页面
- 3、配置 serve 脚本 执行脚本
"scripts": {
"serve": "npx webpack-dev-server --port 3001 --open",
}
当然也可以通过webpack配置文件的 devServer 字段开启 webpack-dev-server 服务
- 1、 配置 devServer 字段
// webpack.config.js
module.exports = {
// 开启watch模式
// watch: false,
// 配置 webpack-dev-server
devServer: {
// 编译完成是否自动打开页面
open: true,
// 服务端口号
port: 3000,
// 是否启用压缩
compress: true,
// 是否启用热更新
hot: true,
// 服务的基准路径
contentBase: "./src"
}
}
- 2、配置 dev 脚本 执行脚本
"scripts": {
"build": "npx webpack --config webpack.custom.config.js",
"watch": "npx webpack --config webpack.config.js --watch",
"serve": "npx webpack-dev-server --port 3001 --open",
"dev": "npx webpack serve"
}
webpack-dev-middleware
webpack-dev-middleware 使用node的中间件,它可以作为一个容器将webpack打包后的资源提供给服务器。webpack-dev-server 也是通过这个实现的
-
安装 express、webpack-dev-middleware
npm i express webpack-dev-middleware -D
-
根目录新建 server.js
// 新建服务 并运行 // 导入express框架 const express = require("express") // 导入webpack const webpack = require("webpack") // 导入中间件 const webpackDevMiddleware = require("webpack-dev-middleware") // 导入webpack配置对象 const config = require("./webpack.config.js") // 新建服务 const app = express() const compiler = webpack(config) // app注册中间件 app.use(webpackDevMiddleware(compiler, { publicPath: "/" })) // 监听服务 app.listen(3001, function () { console.log("3000端口服务运行"); })
-
新增脚本 server, 并执行
"scripts": { "server": "node server.js" }
处理css文件
webpack默认无法处理css文件,我们需要借助相关loader进行处理
-
安装 css-loading
npm i css-loader style-loader -D
-
配置文件中配置loader(webpack.config.js)
// 配置各种loader module: { rules: [ // 配置用来解析css相关loader { // loader匹配到的文件 test: /\.css$/i, // webpack调用loader的顺序是从右到左 use: ['style-loader', 'css-loader'] } ] },
-
新建
src/css/index.css
li { line-height: 100px; background-color: red; }
-
入口文件导入css
html引入的资源文件 webpack不会打包,我们需要将css文件引入到 src/index/js 中// src/index.js // 在webpack的入口文件中引入css import style from './css/index.css'
-
执行
npm run serve
处理less/scss文件
css预处理器的文件 webpack也可以打包处理,这里以 less 为例
- 安装 loader
npm i less-loader -D
- 新建 src/less/index.less
ul { li:nth-child(1) { background-color: pink; color: green; } }
- 在入口文件中导入less
// 在webpack的入口文件中引入less import './less/index.less'
- 配置less-loader (webpack.config.js)
// 配置各种loader module: { rules: [ { test: /\.css$/i, // webpack调用loader的顺序是从右到左 use: ['style-loader', 'css-loader'] }, // 配置用来解析.less相关loader { test: /\.less$/i, // webpack调用loader的顺序是从右到左 use: ['style-loader', 'css-loader', 'less-loader'] } ] }
- 执行
npm run serve
,查看效果
处理图片字体文件
图片和字体文件也需要loader来处理
webpack4
file-loader:处理图片等文件
url-loader:处理图片等文件,可将图片转为base64格式
注意:url-loader 是对 file-loader 的包装,使用 url-loader 必须安装 file-loader
- 安装
npm i fild-loader url-loader -D
- 配置相关loader (webpack.config.js)
module: { rules: [ // 配置用来解析图片等文件相关loader { test: /\.jpg|png|gif|bmp|ttf|eot|svg|woff|wpff2$/i, // 图片大小大于16940自动转为base64格式 use: 'url-loader?limit=16940' // 使用file-loader // use: 'file-loader' }] }
webpack5
webpack5 通过内置的 Asset Modules 引入任何其他类型的文件
1、asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
2、asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
3、asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
4、asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。
- 配置文件(webpack.config.js)
module: { rules: [ // 配置用来解析图片等文件相关loader { test: /\.jpg|png|gif|bmp|ttf|eot|svg|woff|wpff2$/i, type: "asset", //解析 parser: { //转base64的条件 dataUrlCondition: { // 大于100kb转为base64格式 maxSize: 100, // 25kb } }, generator: { //输出路径 // name:使用原文件名 // hash:文件名增加hash字符 // ext:使用原文件拓展名 filename: 'img/[name].[hash:6][ext]', //打包后对资源的引入,文件命名已经有/img了 publicPath: './' } } ] },
配置babel
webpack可以通过babel打包解析高级语法,将它们转换成可兼容绝大多数浏览器的代码
- 安装
npm install babel-loader babel-core babel-preset-env
- 配置 bable-loader (webpack.custom.config.js)
// 配置各种loader module: { rules: [ // 解析所有.js文件 { test: /\.js$/, // 排除node_modules等文件 exclude: /(node_modules|bower_components)/, use: { // 使用 babel-loader loader: 'babel-loader', options: { // 使用 babel的预设 presets: ['@babel/preset-env'] } } } ] }
使用bebal处理generator
js一些最新语法或者处于草案阶段的语法,babel需要借助相应的插件进行处理。
// index.js
......
const zs = new Person({ name: "张三" })
console.log("zs", zs.name);
const init = () => {
console.log("webpack真好玩123fsd")
}
init()
// 使用generator
function* fn() {
yield 1
yield 2
return 3
}
let newFn = fn()
console.log("next1:", newFn.next())
console.log("next2:", newFn.next())
打包后会报错
- 安装 plugin-transform-runtime
npm i @babel/plugin-transform-runtime -D // @babel/runtime 是 @babel/plugin-transform-runtime的核心依赖 npm install --save @babel/runtime
- 配合打包配置 babel-loader (webpack.custom.config.js)
module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], // 注册babel插件 plugins: ["@babel/plugin-transform-runtime"] } } } ] },
npm run build
执行 webpack.custom.config.js
babel配置文件
babel作为一个工具,可能会有大量需要配置的地方,如果在webpack配置babel可能会造成webpack配置文件体量过于庞大、可读性差等问题。这时候可以通过babel的独立配置文件解决这个问题
.babelrc.json
在项目根目录新建 .babelrc.json ,将 babel-loader 的 options 字段移植到 .babelrc.json 中.
// .babelrc.json
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
// webpack.custom.config.js
{
test: /\.js$/,
// 排除node_modules等文件
exclude: /(node_modules|bower_components)/,
use: {
// 使用 babel-loader
loader: 'babel-loader',
// 移植到 .babelrc.json中
// options: {
// // 使用 babel的预设
// presets: ['@babel/preset-env'],
// plugins: ["@babel/plugin-transform-runtime"]
// }
}
}
使用bebal处理实例的原型方法
某些实例的原型方法babel不会进行转换成低级代码,可能会出现兼容性问题。例如数组实例的map、includes等方法。
@babel/polyfill
他是 @babel/plugin-transform-runtime的补丁,只需要安装一下在使用实例原型方法的位置导入进行即可
- 安装
npm i @babel/polyfill -D
- 导入
// src/index.js // 导入polyfill import '@babel/polyfill' const arr = [1, 2, 3] console.log("arr是否包含 '1'", arr.includes(1)); console.log(arr.map(e => e + 2)); ``
sourceMap
Sourcemap
本质上是一个信息文件,里面储存着代码转换前后的对应位置信息。它记录了转换压缩后的代码所对应的转换前的源代码位置。webpack会对源码进行压缩、编译、高版本代码到低版本代码的转换,会导致处理后代码阅读性很差,无法追踪bug错误,Sourcemap就是解决这一问题的利器。Sourcemap可以在生产环境使用但是极不推荐!!会造成源码泄露的风险
devtool
devtool是webpack配置项,用来控制使用sourceMap的方式
[inline-|hiddeb-|eval-][nosources-][cheap-[module-]]source-map
source-map
devtool:source-map
是最基本的用法
特点:
-
外部单独一个sourcemap文件
-
可以提供错误代码准确位置,源代码的错误位置 示例
-
手写一个bug
const a = require("./a") import './css/index.css' import './less/index.less' import img from './img/avatar.jpg' console.log(a.name); class Person { constructor(opt) { this.name = opt.name } } // 手动声明一个错误(在console.log()后加上()) const zs = new Person({ name: "张三" }); console.log("zs", zs.name)();
-
配置 webpack.config.js
const path = require("path") const HtmlPlugin = require("html-webpack-plugin") module.exports = { entry: "./src/index.js", output: { path: path.resolve(__dirname, "./dist"), filename: 'bundle.js' }, mode: "production", plugins: [ new HtmlPlugin( {filename: "index.html",template: "./src/index.html"} ) ], module: { rules: [ { test: /\.css$/i,use: ['style-loader', 'css-loader']}, { test: /\.less$/i, use: ['style-loader', 'css-loader', 'less-loader']}, { test: /\.jpg|png|gif|bmp|ttf|eot|svg|woff|wpff2$/i, type: "asset", parser: { dataUrlCondition: { maxSize: 100, // 25kb } }, generator: { filename: 'img/[name].[hash:6][ext]', publicPath: './' } }, { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: {loader: 'babel-loader' } } ] }, // 开启source-map模式 devtool: "source-map" }
-
执行
npx webpack
构建项目,dist目录下生成.map文件
inline-source-map
特点:
-
内联sourcemap,构建速度快。只生成一个sourcemap
-
可以提供错误代码准确位置,源代码的错误位置 示例:
-
修改 webpack.config.js,将 devtool 改为 "inline-source-map"
devtool: "inline-source-map"
-
删除dist目录,执行 npx webpack 重新构建项目,没有 .map 文件所有sourcemap数据会以base64的形式内嵌到 bundle.js 中
hidden-source-map
特点:
- 外部sourcemap文件
- 可以提示错误代码错误原因,没有错误位置
- 不能追踪源代码错误位置,只能提示构建后错误代码位置
eval-source-map
特点:
- 内联sourcemap,每一个文件都有对应的source-map 都在eval
- 可以提供错误代码信息,源代码的错误位置
nosources-source-map
特点:
- 外部单独一个sourcemap文件
- 可以提供错误代码信息,没有任何源代码的信息
cheap-source-map
特点:
- 外部单独一个sourcemap文件
- 可以提供错误代码信息和源代码错误位置(只精确到行)
cheap-module-source-map
特点:
- 外部单独一个sourcemap文件
- 可以提供错误代码信息和源代码错误位置
开发环境:
开发环境 要求速度快、调试友好
速度:
eval > inline > cheap > ...
eval-cheap-source-map>eval-source-map
调试:
source-map
cheap-module-source-map
cheap-source-map
建议:
eval-source-map(MVVM框架一般用这个) / eval-cheap-module-source-map
生产环境
生产环境要考虑代码是否要隐藏、调试友好,不能使用内联,内联会让代码体积变大
nosources-source-map
hidden-source-map
建议
source-map / cheap-module-source-map
clean-webpack-plugin
webpack每次构建生成新的dist目录,默认不会清空dist下的文件,这对导致有些缓存文件一直存在,clean-webpack-plugin会在构建项目前清空dist
- 安装
npm i clean-webpack-plugin -D
- 配置plugin
// webpack.custom.config.js const CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin module.exports = { plugins: [ new HtmlPlugin( { filename: "index.html", template: "./src/index.html" } ), // new插件实例 new CleanWebpackPlugin() ], }
copy-webpack-plugin
webpack默认打包src下的所有资源,目录外的资源可以通过 copy-webpack-plugin 打包进来
- 安装
npm i copy-webpack-plugin -D
- 配置plugin
// webpack.custom.config.js { plugins:[ new CopyWebpackPlugin({ patterns: [{ // 将项目的assets目录复制一份到dist/assets中 from: path.join(__dirname, "assets"), to: 'assets' }] }) ] }
BannerPlugin
这是webpack内部的一个插件,用来为打包js添加注释、版本等信息
- 在 webpack.custom.config.js 中
// 导入webpack const Webpack = require("webpack") module.exports = { plugins: [ new Webpack.BannerPlugin("这是一段版权信息") ] }