webpack
一、快速上手
1、yarn init
2、yarn add webpack webpack-cli --dev
3、yarn webpack --version
4、yarn webpack
const path = require('path')
module.exports = {
// 这个属性有三种取值,分别是 production、development 和 none。
// 1. 生产模式下,Webpack 会自动优化打包结果;
// 2. 开发模式下,Webpack 会自动优化打包速度,添加一些调试过程中的辅助;
// 3. None 模式下,Webpack 就是运行最原始的打包,不做任何额外处理;
mode: 'none',
entry: './src/main.css', //入口文件
output: { //出口文件
filename: 'bundle.js', //出口文件名
path: path.join(__dirname, 'dist') //出口路径
}
}
5、安装加载器 loader
样式文件
yarn add css-loader --dev
yarn add style-loader --dev
入口文件还是main.js,然后css文件import 引入
文件
yarn add file-loader --dev //导出来的是路径
yarn add rul-loaer --dev //导出来的是文件
附:小文件使用DataURLs,减少请求次数
打文件单独提取存放,提高加载速度
超出10kb的单独存放
小于10kb文件转换为Data URLs 嵌入代码中
js编译
yarn add babel-loader --dev
yarn add @babel/core @babel/preset-env --dev
html编译
yarn add html-loader --dev
const path = require('path')
module.exports = {
// 这个属性有三种取值,分别是 production、development 和 none。
// 1. 生产模式下,Webpack 会自动优化打包结果;
// 2. 开发模式下,Webpack 会自动优化打包速度,添加一些调试过程中的辅助;
// 3. None 模式下,Webpack 就是运行最原始的打包,不做任何额外处理;
mode: 'none',
entry: './src/main.js', //入口文件为为了css
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
module: {
rules: [
{
test:/.js$/,
//use:'babel-loader'
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
},
{
test:/.css$/,
use:'css-loader'
},
{
test: /.png$/,
use: 'url-loader' //'file-loader'
},
{
test: /.png$/,
//use: 'url-loader'
use: {
loader: 'url-loader',
options:{
limit: 10*1024
}
}
},
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'a:href']
}
}
}
]
}
}
总结:编译转换类、文件操作类、代码检查类
- file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
- url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
- source-map-loader:加载额外的 Source Map 文件,以方便断点调试
- image-loader:加载并且压缩图片文件
- babel-loader:把 ES6 转换成 ES5
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
- style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
- eslint-loader:通过 ESLint 检查 JavaScript 代码
二、模块加载方式:
1、遵循ES Modules 标准的 import 声明
2、遵循CommonJS标准的require函数
3、遵循AMD 标准的define函数和require 函数
4、*样式代码中的@import指令和url函数
5、*HTML 代码中图片标签的src属性
三、核心工作原理
一个项目中会有各种的资源文件,webpack会根据配置找到其中的一个入口文件,一般是一个javascriptw文件,
通过require、import等解析依赖的资源模块,会整理成一个依赖关系树,webpack会递归依赖树,找到每个节点对应的资源文件,再根据配置文件中的rules属性找到这个模块所对应的加载器,然后交给加载器去加载模块,最后将加载后的结果放在bundle中,从而完成打包。
四、自定义一个loader
1、yarn init
2、yarn add webpack webpack-cli --dev
3、创建一个markdown-loader.js,作为自定义loader,可以后期放在npm 上
const marked = require('marked')
module.exports = source => {
console.log(source);
//return 'console.log("hello")'; //只能返回一个javascript代码,或者交给下一个loader 处理
const html = marked(source)
//return html //只能返回一个javascript代码
//return `module.exports = ${JSON.stringify(html)}`
//return `export default ${JSON.stringify(html)}`
return html
}
5、webpack.config.js
const path = require('path')
module.exports = {
mode: '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'
]
}
]
}
}
五、webpack插件机制
插件是增强webpack自动化能力
loader专门实现资源模块加载,实现整体项目的打包
1、clean-webpack-plugin 清除插件
yarn add clean-webpack-plugin --dev
添加 const { CleanWebpackPlugin } = require(‘clean-webpack-plugin’)
添加plugins属性,是一个数组,new实例
2、自动生成使用bundle.js的html ,自动生成index.html html-webpack-plugin
yarn add html-webpack-plugin --dev
const HtmlWebpackPlugin = require(‘html-webpack-plugin’)
new HtmlWebpackPlugin({
title:‘yuan’, //设置属性
mate:{
viewport :‘width=device-width, initial-scale=1’
},
template: ‘./src/index.html’ //添加模板,在src下建立一个index.html文件
})
//index.html
<%= htmlWebpackPlugin.options.title %>
执行yarn webpack 后,dist文件夹会生成一个index.html,里边的,路径错误,这是因为publicPath: ‘dist/’,注释掉即可
3、拷贝那些不需要参与打包的资源文件到输出目录 copy-webpack-plugin
这里需要注意的是,当静态文件过多时,每次都会进行打包输出,会影响webpack的打包效率,所以这个插件建议在上线之前使用,不建议在开发阶段使用
4、开发一个webpack插件
webpack中的插件是通过钩子机制进行实现的,自定义一个webpack插件时,默认是一个函数或者是一个包含apply方法的对象
这里我们做一个清除多余注释的插件
// 定义插件
class myPlugin {
apply(compiler) {
console.log('myPlugin 启动')
// 这个钩子函数是在写入文件后执行
compiler.hooks.emit.tap('myPlugin', compilation => {
// 此次打包的上下文
for(const name in compilation.assets) {
if(name.endsWith('.js')) {
// 写入文件的内容
const context = compilation.assets[name].source()
// 通过正则表达式对其进行修改
const withoutComment = context.replace(/\/\*\*+\*\//g, '')
compilation.assets[name] = {
// 返回的文件内容
source: () => withoutComment,
// 文件大小,webpack必须的参数
size: () => withoutComment.length
}
}
}
})
}
}
在下面进行引用,可以看到调用webpack打包时,后缀为.js的文件中的多余注释被清除掉了
六、增强webpack的开发体验
1.webpack自动编译
通过 yarn webpack --watch的方式监视文件,实现webpack自动编译
2.webpack自动刷新浏览器
通过broswer-sync实现
不过上面两种实现方式,在操作上需要使用两个依赖,在操作上更加复杂了,这里我们通过webpack-dev-server实现上述两个功能
首先安装webpack-dev-server,随后运行这个工具
// 安装
yarn add webpack-dev-server --dev
//使用
yarn webpack-dev-server
// 自动打开浏览器
yarn webpack-dev-server --open
这个依赖包可以自动启动一个本地服务器,并同时进行watch,大大提高了webpack的开发体验
需要注意的是,在使用这个依赖时,webpack为了提高效率,暂时不会对src目录下的文件进行打包输出到dist目录下,而是暂时存储到内存中
由于上述copyWebpackPlugin这个插件不建议在开发阶段使用,当我们需要访问静态资源时,需要在配置文件中指定静态资源的路径
devServer: {
contentBase: './public'
},
通过对配置文件contentBase的设定,可以解决静态资源访问的问题
3.webpack-dev-server配置代理解决跨域问题
当CORS(跨域资源共享)无法满足webpack在本地访问api的跨域问题时,webpack-dev-server可以通过配置代理解决跨域问题,这里我们默认请求的是api/github.com中的users接口
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
因为生产环境的代码或者是开发阶段运行的代码都是通过原本的代码转换后的,两者对比会不相同,这就导致调试问题的时候无法定位,source map可以解决,通过配置文件中的添加devtool,并设置成source-map
devtool : ‘source-map’
支持12中方式,其中eval,是在budle.js中生成源代码
![](https://img-blog.csdnimg.cn/img_convert/0d719c86deecd4481b00ce0e02fd06ee.png#align=left&display=inline&height=424&margin=[object Object]&originHeight=424&originWidth=702&status=done&style=none&width=702)
定义sourceMap的方式从四方面考虑,构建速度、打包速度、构建质量、适用于生产情况
一般我们在开发阶段推荐使用:cheap-module-eval-source-map,保证开发中每行代码不超过80个字符,并且依赖框架开发,对构建之前的源代码要求较高,同时对首次打包速度要求不高的情况下选择
在生产环境中,不建议生成sourceMap,或者使用nosources-source-map,保证自己源代码不暴露给其他人
5.webpack 的HMR
当代码发生变化时,webpack就会自动更新,就会刷新,就会导致当前编辑的内容刷新掉,HMR(Hot Module Replacement) 模块热替换便是webpack解决该问题的最有效的方式,应用运行过程中,实时替换某个模块,应用运行状态不受影响。
HMR是集成在webpack-dev-server中,可以直接在webpack.config.js中进行配置,也可以在启动时 yarn webpack-dev-server --hot
const webpack = require('webpack')
devServer: {
hot: true,
}
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Tutorial',
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin()
]
这种方式可以实现部分的热更新,js和img的热更新还不行,我们还得通过特殊的处理
思想:移除原来的,记录更新的,赋值
if (module.hot) {
let hotEditor = editor
module.hot.accept('./editor.js', () => {
// 当 editor.js 更新,自动执行此函数
// 临时记录编辑器内容
const value = hotEditor.innerHTML
// 移除更新前的元素
document.body.removeChild(hotEditor)
// 创建新的编辑器
// 此时 createEditor 已经是更新过后的函数了
hotEditor = createEditor()
// 还原编辑器内容
hotEditor.innerHTML = value
// 追加到页面
document.body.appendChild(hotEditor)
})
module.hot.accept('./better.png', () => {
// 当 better.png 更新后执行
// 重写设置 src 会触发图片元素重新加载,从而局部更新图片
img.src = background
})
// style-loader 内部自动处理更新样式,所以不需要手动处理样式模块
}
注意事项:
1、如果在热更新的时候有错误出现,将
devServer: {
hotOnly: true,
}
2、HMR 手动处理模块热更新,不用担心这些代码在生产环境冗余的问题,因为通过 webpack 打包后, 这些代码全部会被移除,这些只是开发阶段用到
6.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: {
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
}
2、不同的环境,配置不同的配置文件
首先新建一个webpack.common.js的文件,把基础公共的操作放在里边,然后再创建一个webpack.prod.js,把生产的一些配置放写里边,然后再创建一个webpack.dev.js,开发阶段的
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.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.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()
]
})
如果最后要打包生产包,执行
yarn webpack --config webpack.prod.js
7.webpack DefinePlugin
为代码注入全局成员,在production下,插件默认启动起来,并且注入一个全局变量,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')
})
]
}
8.webpack Tree Shaking
去掉没用的代码来减少包的大小,提高打包效率,在production中默认开启
通过配置optimization属性,usedExports : true,导出只被使用的;minimize:true,压缩输出结果; concatenateModules: true,尽可能把所有的模块合并到一个函数中
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: 'bundle.js'
},
optimization: {
// 模块只导出被使用的成员
usedExports: true,
// 尽可能合并每一个模块到一个函数中
concatenateModules: true,
// 压缩输出结果
// minimize: true
}
}
9.webpack Tree Shaking和babel
如果在项目中使用babel-lodaer,有可能会导致tree shaking失效,因为tree-shaking的前提是ES Modules,如果使用了babel中的@babel/preset-env,有可能会把ES Modules转换成CommonJS,所以tree-shaking会失效,但是在最新的babel-loader中,可以选择如下配置[’@babel/preset-env’, { modules: false }]
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
// 如果 Babel 加载模块时已经转换了 ESM,则会导致 Tree Shaking 失效
// ['@babel/preset-env', { modules: 'commonjs' }]
// ['@babel/preset-env', { modules: false }]
// 也可以使用默认配置,也就是 auto,这样 babel-loader 会自动关闭 ESM 转换
['@babel/preset-env', { modules: 'auto' }]
]
}
}
}
]
},
optimization: {
// 模块只导出被使用的成员
usedExports: true,
// 尽可能合并每一个模块到一个函数中
// concatenateModules: true,
// 压缩输出结果
// minimize: true
}
}