小技巧:折起代码,command+k+0
webpack,是一个打包儿的工具,具体的某项功能一般通过不同类型的加载器,或者插件实现,一般分为这几类:
分割
提示:babel只是一个转换新特性的工具集合,具体使用还要指定插件
webpack加载资源的方式,以下都支持:
但是使用时最好统一使用一种,别混用,不利于代码的维护和使用;
每一个loader都想一个管道,将一个输入处理加工然后输出
处理markdown文件的loader;
const marked = require('marked');
// loader文件,负责资源文件的从输入到输出的转换,
// 他也是一种管道的概念,可以将这个loader的输出交给下一个loader去输入处理,可以依次执行多个loader
module.exports = source => {
let html = marked(source);
html = JSON.stringify(html); //可以转移"号
console.log(html);
// 必须返回一段js代码,会直接放入模块儿中
return `module.exports = ${html}`
}
帮我实现处理加载器loader模块之外的其他工作;
1、clean-webpack-plugin,清除打包目录
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
...
plugins:[
new CleanWebpackPlugin()
]
2、html-webpack-plugin,生成html文件
const HtmlWebpackPlugin = require('html-webpack-plugin')
...
new HtmlWebpackPlugin({
filename: 'about.html',
template: './src/index.html'
})
3、copy-webpack-plugin,复制文件
const CopyWebpackPlugin = require('copy-webpack-plugin')
...
new CopyWebpackPlugin([
// 'public/**'
'public'
])
Plugin通过钩子机制实现
Webpack几乎给每一个过程都添加了钩子函数,第三方插件就可以通过这些钩子来给webpack挂在不同的任务,
Webpack要求插件必须是一个函数或者是一个包含apply方法的对象;
需求去除bundle.js中的无用js
webpack插件
class MyPlugin {
apply(compiler) { // apply方法
console.log('my Plugin');
// emit钩子资源输出到打包目录前
compiler.hooks.emit.tap('myplugin', compilation => {
let source = compilation.assets;
// 循环出所有资源
for( let name in source){
// 找到js资源
if(name.endsWith('.js')){
let content = source[name].source();
// 替换/******/信息
let content2 = content.replace(/\/\*\*+\*\//g, '');
source[name] = {
source: () => content2,
size: () => content2.length
}
}
}
})
}
}
原理就是,通过在生命周期的钩子中挂载函数实现扩展;
关键钩子 | 钩子类型 | 钩子参数 | 作用 |
---|---|---|---|
beforeRun | AsyncSeriesHook | Compiler | 运行前的准备活动,主要启用了文件读取的功能。 |
run | AsyncSeriesHook | Compiler | “机器”已经跑起来了,在编译之前有缓存,则启用缓存,这样可以提高效率。 |
beforeCompile | AsyncSeriesHook | params | 开始编译前的准备,创建的ModuleFactory,创建Compilation,并绑定ModuleFactory到Compilation上。同时处理一些不需要编译的模块,比如ExternalModule(远程模块)和DllModule(第三方模块)。 |
compile | SyncHook | params | 编译了 |
make | AsyncParallelHook | compilation | 从Compilation的addEntry函数,开始构建模块 |
afterCompile | AsyncSeriesHook | compilation | 编译结束了 |
shouldEmit | SyncBailHook | compilation | 获取compilation发来的电报,确定编译时候成功,是否可以开始输出了。 |
emit | AsyncSeriesHook | compilation | 输出文件了 |
afterEmit | AsyncSeriesHook | compilation | 输出完毕 |
done | AsyncSeriesHook | Stats | 无论成功与否,一切已尘埃落定。 |
1、webpack-dev-server 开发服务器插件
// 服务配置,
devServer: {
// 静态资源目录,可以配置数组,webpack会一个一个去找
contentBase: './public', // 公共资源目录,可以是数组一个一个找
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 作为请求 GitHub 的主机名 设为true后就会以后目标域名及https://api.github.com请求
changeOrigin: true
}
}
},
注意:webpack-dev-server 下载完后可能会运行报错,可以将其三个一起安装 npm install webpack webpack-cli webpack-dev-server -D
2、添加 Source Map,资源地图,方便调试;
Webpack中的Source Map有多钟模式,没有种模式都有不同的效果;
devtool: 'eval',
3、自动刷新的问题:
问题核心:自动刷新导致的页面状态丢失问题;
是否可以:页面不刷新的前提下,模块也可以及时更新;
Webpack中的HMR,模块儿热替换,在不影响应用运行的状态下,更新模块儿变化;
HMR是Webpack中最强大的功能之一,极大程度的提高了开发者的工作效率;
使用;通过 webpack-dev-server --hot,添加–hot参数
或者
const webpack = require('webpack')
...
devServer :{
hot:true, //模块热替换
},
plugins:{
new webpack.HotModuleReplacementPlugin()
}
我们还需要手动处理JS模块更新后的热替换,具体方法可以在模块代码中手动处理
import hello from './module1.js'
module.hot.accept('./module1.js', () => {
// 这里处理热更新模式
})
注意事项:
1、热替换代码如果出现错误,是不容易发现的,建议使用hotOnly配置
hotOnly: true
2、没启用HMR的情况下,HMR API报错,使用前需要先判断
if(module.hot){
module.hot.accept('./module1.js', () => {
// 这里处理热更新模式
})
}
3、代码中多了一些与业务无关的代码
打包配置,不设置HMR时,代码中的module.hot会判断为false。
webpack打包时就会去除,不会打包儿到生成环境。
手动处理热替换会比较麻烦,小型项目不适使用,大型项目或者长期项目较为使用,一般大型框架都会自己封装现成的HMR,像vue-cli、react-cli等
webpack.config.js文件也可以导出一个函数返回配置
module.exports = (env, argv) => {
// 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 --env production
大型项目中,一般使用不同环境对应不同配置文件,多配置文件
webpack.common.js
webpack.dev.js
webpack.prod.js
// webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge') //专门合拼webpack配置的插件
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 --config webpack.dev.js
1、DefinePlugin默认插件,为我们注入了全局变量,默认注入了process.env.NODE_ENV
plugins: [
new webpack.DefinePlugin({
// 值要求的是一个代码片段,API_BASE_URL就可以再业务代码中调用
API_BASE_URL: JSON.stringify('https://api.example.com')
})
]
console.log(API_BASE_URL)
2、Tree Shaking”摇树“,去除代码中的无用代码(未被引用的代码dead-code),冗余代码,webpack生成模式自动启动,其他模式要使用可以配置
optimization: { //配置webpack内部的优化配置的
usedExports: true, //只导出引用到的模块
concatenateModules: true, //尽可能合并每一个模块到一个函数中,进一步减少代码体积,合并模块
minimize: true // 最小压缩
}
Tree Shaking与Babel的问题;
Tree Shaking是基于ES6的ESModules的,而Babel在编译时会将ESModules转为->CommonJS,致使Tree Shaking不能生效及usedExports: true不能生效;
解决;
最新版的Babel已经自动用关闭了ESModules到CommonJS转换的插件,所以使用最新的是可以使Tree Shaking生效的
或者使用
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' }]
]
}
}
}
]
},
使用@babel/preset-env 的不同配置来关闭ESModules的转换
3、sideEffects(副作用)
副作用:指模块执行时除了导出成员之外所作的其他事情;
sideEffects一般用于npm包标记是否有副作用;
// extend.js
// 为 Number 的原型添加一个扩展方法,没有导出成员
Number.prototype.pad = function (size) {
// 将数字转为字符串 => '8'
let result = this + ''
// 在数字前补指定个数的 0 => '008'
while (result.length < size) {
result = '0' + result
}
return result
}
// 除了导出成员的代码都属于副作用
// 像这样的模块都属于副作用样式文件属于副作用模块
import './global.css'
// extend都属于副作用模块
import './extend'
副作用要慎用,确保你的代码没有副作用
使用:
optimization: { //配置webpack内部的优化配置的
sideEffects: true //副作用生成模式下会自动开启,开启之后,会先去当前项目的package.json查看当前代码是否有副作用,没有的话,就不会打包儿无用的模块。
}
// package.json
"sideEffects": false //当为false没有副作用可以使用sideEffects,当为true时,有副作用不可用sideEffects。
// 或者配置某些文件不参与副作用
"sideEffects": [
"./src/extend.js",
"*.css"
]
注意:Tree Shaking和sideEffects,有相同的部分,但是不完全一样,Tree Shaking是未被引用的代码,sideEffects是未被导出的其他部分代码,sideEffects要慎用。
webpack配置
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
// 配置多个入口
entry: {
index: './src/index.js',
album: './src/album.js'
},
output: {
filename: '[name].bundle.js' //[name]可动态指定文件名
},
optimization: { //webpack内部配置
splitChunks: {
// 自动提取所有的公共模块到单独的 bundle中
chunks: 'all'
}
}
plugins: [
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/index.html',
filename: 'index.html',
chunks: ['index'] //不同的页面注入不同的js文件块儿
}),
new HtmlWebpackPlugin({
title: 'Multi album',
template: './src/album.html',
filename: 'album.html',
chunks: ['album'] //不同的页面注入不同的js文件块儿
})
]
}
需要动态载入模块时,需要引入模块儿时,使用import函数
import posts from './posts/posts'
import album from './album/album'
// -------替换为------------
import('./posts/posts').then(({default: posts}) => {
//posts.....后续事情
})
import('./album/album').then(({default: album}) => {
//album.....后续事情
})
魔法注释:
通过在函数中添加/* webpackChunkName: ‘components’ */,可以为每个模块命名,并且相同名字的模块就会被打包到一起。
import(/* webpackChunkName: 'posts' */'./posts/posts').then(({default: posts}) => {
//posts.....后续事情
})
import(/* webpackChunkName: 'album' */'./album/album').then(({default: album}) => {
//album.....后续事情
})
mini-css-extract-plugin插件,提取样式为单独的css文件,建议css较大时使用
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
...
module: {
rules: [
{
test: /\.css$/,
use: [
// 'style-loader', // 将样式通过 style 标签注入
// 使用MiniCssExtractPlugin时,这里就不是通过style标签注入了,而是通过link标签引入,所以这里使用MiniCssExtractPlugin通过的loader来处理
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin()
]
...
optimize-css-assets-webpack-plugin插件,压缩css文件,webapck默认只会压缩js代码,css不会压缩,所以使用这个插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
...
optimization: {
minimizer: [
new TerserWebpackPlugin(),
new OptimizeCssAssetsWebpackPlugin(),//官方建议添加到这里可以根据默认配置,生产环境时再压缩,但是也有问题,就是配置了minimizer属性之后,webpack会认为你要自定义压缩模式,就不会压缩js代码了。
// 这里你要自己添加webpack内置的js压缩插件terser-webpack-plugin
]
},
plugins: [
new MiniCssExtractPlugin(),
// new OptimizeCssAssetsWebpackPlugin() //添加到这里无论在那种环境下都会压缩css
]
...
1、一般的filename都支持Hash值
‘[name]-[hash].bundle.js’ 项目级别hash整个项目hash都会变
output: {
filename: '[name]-[hash].bundle.js'
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name]-[hash].bundle.css'
})
]
2、’[name]-[chunkhash].bundle.js’ 模块儿hash,相互关联的模块儿hash相同
3、’[name]-[contenthash].bundle.js’ 根据模块内容生成的hash,不同的内容不同的hash
4、’[name]-[contenthash:8].bundle.js’ 通过:8可以指定hash长度,一般就是8位就可以了。