最近朋友圈被《头号玩家》刷爆了,斯皮尔伯格一个资深电影导演,把对过去经典的致敬,对将来的憧憬浓缩在这一部电影中,能够说让观众燃了起来。css
观望整个前端开发,不断的演化,发展迅速。前端开发从最开始切页面, 前端自动化构建工具突飞猛进,从最初的Grunt,Gulp到如今前端项目能够说标配的webpack。html
咱们先来致敬经典: 前端
能够看作一个模块化打包机,分析项目结构,处理模块化依赖,转换成为浏览器可运行的代码。node
构建把一系列前端代码自动化去处理复杂的流程,解放生产力。react
npm install webpack webpack-cli -D
复制代码
webpack4抽离出了webpack-cli,因此咱们须要下载2个依赖。webpack
Webpack 启动后会从Entry里配置的Module开始递归解析 Entry 依赖的全部 Module。 每找到一个 Module, 就会根据配置的Loader去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其全部依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把全部 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。git
webpack须要在项目根目录下建立一个webpack.config.js来导出webpack的配置,配置多样化,能够自行定制,下面讲讲最基础的配置。github
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, './dist'),
filename: 'main.js',
}
}
复制代码
entry
表明入口,webpack会找到该文件进行解析output
表明输入文件配置path
把最终输出的文件放在哪里filename
输出文件的名字有时候咱们的项目并非spa,须要生成多个js html,那么咱们就须要配置多入口。web
module.exports = {
entry: {
pageA: './src/pageA.js',
pageB: './src/pageB.js'
},
output: {
path: path.join(__dirname, './dist'),
filename: '[name].[hash:8].js',
},
}
复制代码
entry
配置一个对象,key值就是chunk
: 代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。看看filename[name]
: 这个name指的就是chunk的名字,咱们配置的key值pageA``pageB
,这样打包出来的文件名是不一样的,再来看看[hash]
,这个是给输出文件一个hash值,避免缓存,那么:8
是取前8位。正则表达式
这里有人会有疑问了,项目是多页面的,应该有pageA.html``pageA.js``pageA.css
, 那么我应该生成多个html,这个只是作了JS的入口区分,我不想每个页面都去复制粘贴一个html,而且html是大部分重复的,可能不一样页面只须要修改title
,下面来看看这个问题怎么解决:
须要引入一个webpack的plugin
:
npm install html-webpack-plugin -D
复制代码
该插件能够给每个chunk生成html,指定一个template
,能够接收参数,在模板里面使用,下面来看看如何使用:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
pageA: './src/pageA.js',
pageB: './src/pageB.js'
},
output: {
path: path.join(__dirname, './dist'),
filename: '[name].[hash:8].js',
},
plugins: [
new HtmlWebpackPlugin({
template: './src/templet.html',
filename: 'pageA.html',
title: 'pageA',
chunks: ['pageA'],
hash: true,
minify: {
removeAttributeQuotes: true
}
}),
new HtmlWebpackPlugin({
template: './src/templet.html',
filename: 'pageB.html',
title: 'pageB',
chunks: ['pageB'],
hash: true,
minify: {
removeAttributeQuotes: true
}
}),
]
}
复制代码
在webpack中,插件的引入顺序没有规定,这个在后面在继续详说。
template
: html模板的路径地址filename
: 生成的文件名title
: 传入的参数chunks
: 须要引入的chunkhash
: 在引入JS里面加入hash值 好比: <script src='index.js?2f373be992fc073e2ef5'></script>
removeAttributeQuotes
: 去掉引号,减小文件大小<script src=index.js></script>
这样在dist目录下就生成了pageA.html和pageB.html而且经过配置chunks,让pageA.html里加上了script标签去引入pageA.js。那么如今还剩下css没有导入,css须要借助loader去作,因此如今要下载几个依赖,以scss为例,less同理
npm install css-loader style-loader sass-loader node-sass -D
复制代码
css-loader
: 支持css中的importstyle-loader
: 把css写入style内嵌标签sass-loader
: scss转换为cssnode-sass
: scss转换依赖来看看如何配置loader
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
exclude: /node_modules/
}
]
}
}
复制代码
test
: 一个正则表达式,匹配文件名use
: 一个数组,里面放须要执行的loader,倒序执行,从右至左。exclude
: 取消匹配node_modules里面的文件若是想把css做为一个单独的文件,须要用到一个插件来作(webpack4.0.0以上版本须要next版本):
npm i extract-text-webpack-plugin@next -D
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, './dist'),
filename: 'main.js',
},
module: {
rules: [
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
// style-loader 把css直接写入html中style标签
fallback: 'style-loader',
// css-loader css中import支持// loader执行顺序 从右往左执行
use: ['css-loader', 'sass-loader']
}),
exclude: /node_modules/
}
]
},
plugins: [
new ExtractTextPlugin('[name].[contenthash:8].css'),
]
}
复制代码
name
: chunk名字 contenthash:8
: 根据内容生成hash值取前8位use
: fallback
: 兼容方案这样就实现了js,html,css的打包,那么再来看看一些经常使用的loader:
babel-loader
: 用babel转换代码
url-loader
: 依赖于file-loader
,把图片转换成base64嵌入html,若是超出必定阈值则交给file-loader
rules: [
// 处理js
{
test: /\.js?$/,
exclude: /node_modules/,
use: ['babel-loader']
},
// 处理图片
{
test: /\.(png|jpg|gif|ttf|eot|woff(2)?)(\?[=a-z0-9]+)?$/,
use: [{
loader: 'url-loader',
options: {
query: {
// 阈值 单位byte
limit: '8192',
name: 'images/[name]_[hash:7].[ext]',
}
}
}]
},
]
复制代码
babel的配置建议在根目录下新建一个.babelrc文件
{
"presets": [
"env",
"stage-0",
"react"
],
"plugins": [
"transform-runtime",
"transform-decorators-legacy",
"add-module-exports"
]
}
复制代码
presets
: 预设, 一个预设包含多个插件 起到方便做用 不用引用多个插件env
: 只转换新的句法,例如const let => ..等 不转换 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise、Object.assign。stage-0
: es7提案转码规则 有 0 1 2 3 阶段 0包含 1 2 3里面的全部react
: 转换react jsx语法plugins
: 插件 能够本身开发插件 转换代码(依赖于ast抽象语法数)transform-runtime
: 转换新语法,自动引入polyfill插件,另外能够避免污染全局变量transform-decorators-legacy
: 支持装饰器add-module-exports
: 转译export default {}; 添加上module.exports = exports.default 支持commonjs由于咱们在文件名中加入hash值,打包屡次后dist目录变得很是多文件,没有删除或覆盖,这里能够引入一个插件,在打包前自动删除dist目录,保证dist目录下是当前打包后的文件:
plugins: [
new CleanWebpackPlugin(
// 须要删除的文件夹或文件
[path.join(__dirname, './dist/*.*')],
{
// root目录
root: path.join(__dirname, './')
}
),
]
复制代码
指定extension以后能够不用在require或是import的时候加文件扩展名,会依次尝试添加扩展名进行匹配:
resolve: {
extensions: ['.js', '.jsx', '.scss', '.json'],
},
复制代码
天下武功惟快不破,优化方案千千万万,各取所需吧。
webpack4中废弃了webpack.optimize.CommonsChunkPlugin插件,用新的配置项替代
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, './dist'),
filename: 'main.js',
},
optimization: {
splitChunks: {
cacheGroups: {
commons: {
chunks: 'initial',
minChunks: 2,
maxInitialRequests: 5,
minSize: 2,
name: 'common'
}
}
}
},
}
复制代码
把屡次import的文件打包成一个单独的common.js
在webpack运行在node中打包的时候是单线程去一件一件事情的作,HappyPack能够开启多个子进程去并发执行,子进程处理完后把结果交给主进程
npm i happypack -D
复制代码
须要改造一下loader配置,此loader用子进程去处理
const HappyPack = require('happypack');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, './dist'),
filename: 'main.js',
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: 'happypack/loader?id=babel',
},
]
},
plugins: [
new HappyPack({
id: 'babel',
threads: 4,
loaders: ['babel-loader']
}),
]
}
复制代码
id
: id值,与loader配置项对应threads
: 配置多少个子进程loaders
: 用什么loader处理若是你的项目是用ES2015的模块语法,而且webpack3+,那么建议启用这一插件,把全部的模块放到一个函数里,减小了函数声明,文件体积变小,函数做用域变少。
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, './dist'),
filename: 'main.js',
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
]
}
复制代码
方便长期缓存第三方的库,新建一个入口,把第三方库做为一个chunk,生成vendor.js
module.exports = {
entry: {
main: './src/index.js',
vendor: ['react', 'react-dom'],
},
}
复制代码
第三库不是常常更新,打包的时候但愿分开打包,来提高打包速度。打包dll须要新建一个webpack配置文件,在打包dll的时候,webpack作一个索引,写在manifest文件中。而后打包项目文件时只须要读取manifest文件。
webpack.vendor.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: {
vendor: ['react', 'react-dom'],
},
output: {
path: path.join(__dirname, './dist'),
filename: 'dll/[name]_dll.js',
library: '_dll_[name]',
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, './dist/dll', 'manifest.json'),
name: '_dll_[name]',
}),
]
};
复制代码
path
: manifest文件的输出路径 name
: dll暴露的对象名,要跟output.library保持一致 context
: 解析包路径的上下文,这个要跟接下来配置的dll user一致
webpack.config.js
module.exports = {
entry: {
main: './src/index.js',
vendor: ['react', 'react-dom'],
},
plugins: [
new webpack.DllReferencePlugin({
manifest: path.join(__dirname, './dist/dll', 'manifest.json')
})
]
}
复制代码
html
<scriptsrc="vendor_dll.js"></script>
复制代码
在生产环境和开发环境其实咱们的配置是存在相同点,和不一样点的,为了处理这个问题,会建立3个文件:
webpack.base.js
: 共同的配置webpack.dev.js
: 在开发环境下的配置webpack.prod.js
: 在生产环境的配置经过webpack-merge去作配置的合并,好比:
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const base = require('./webpack.base');
const dev = {
devServer: {
contentBase: path.join(__dirname, '../dist'),
port: 8080,
host: 'localhost',
overlay: true,
compress: true,
open:true,
hot: true,
inline: true,
progress: true,
},
devtool: 'inline-source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
]
}
module.exports = merge(base, dev);
复制代码
开发环境中咱们能够启动一个devServer静态文件服务器,预览咱们的项目,引入base配置文件,用merge去合并配置。
contentBase
: 静态文件地址port
: 端口号host
: 主机overlay
: 若是出错,则在浏览器中显示出错误compress
: 服务器返回浏览器的时候是否启动gzip压缩open
: 打包完成自动打开浏览器hot
: 模块热替换 须要webpack.HotModuleReplacementPlugin
插件inline
: 实时构建progress
: 显示打包进度devtool
: 生成代码映射,查看编译前代码,利于找bugwebpack.NamedModulesPlugin
: 显示模块的相对路径再来看看生产环境最重要的代码压缩,混淆:
const path = require('path');
const merge = require('webpack-merge');
const WebpackParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
const base = require('./webpack.base');
const prod = {
plugins: [
// 文档: https://github.com/gdborton/webpack-parallel-uglify-pluginnew WebpackParallelUglifyPlugin(
{
uglifyJS: {
mangle: false,
output: {
beautify: false,
comments: false
},
compress: {
warnings: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true
}
}
}
),
]
}
module.exports = merge(base, prod);
复制代码
webpack-parallel-uglify-plugin
能够并行压缩代码,提高打包效率
uglifyJS配置:
mangle
: 是否混淆代码output.beautify
: 代码压缩成一行 true为不压缩 false压缩output.comments
: 去掉注释compress.warnings
: 在删除没用到代码时 不输出警告compress.drop_console
: 删除consolecompress.collapse_vars
: 把定义一次的变量,直接使用,取消定义变量compress.reduce_vars
: 合并屡次用到的值,定义成变量想要成为头号玩家,玩转配置可不行,固然还要作一些loader和plugin的开发,去为项目作一些优化,解决痛点。
loader是一个模块导出函数,在正则匹配成功的时候调用,webpack把文件数组传入进来,在this上下文能够访问loader API
this.context
: 当前处理文件的所在目录,假如当前 Loader 处理的文件是 /src/main.js,则 this.context 就等于 /src。this.resource
: 当前处理文件的完整请求路径,包括 querystring,例如 /src/main.js?name=1。this.resourcePath
: 当前处理文件的路径,例如 /src/main.js。this.resourceQuery
: 当前处理文件的 querystring。this.target
: 等于 Webpack 配置中的 Targetthis.loadModule
: 但 Loader 在处理一个文件时,若是依赖其它文件的处理结果才能得出当前文件的结果时, 就能够经过 - - - this.loadModule(request: string, callback: function(err, source, sourceMap, module)) 去得到 request 对应文件的处理结果。this.resolve
: 像 require 语句同样得到指定文件的完整路径,使用方法为 resolve(context: string, request: string, callback: function(err, result: string))。this.addDependency
: 给当前处理文件添加其依赖的文件,以便再其依赖的文件发生变化时,会从新调用 Loader 处理该文件。使用方法为 addDependency(file: string)。this.addContextDependency
: 和 addDependency 相似,但 addContextDependency 是把整个目录加入到当前正在处理文件的依赖中。使用方法为 addContextDependency(directory: string)。this.clearDependencies
: 清除当前正在处理文件的全部依赖,使用方法为 clearDependencies()。this.emitFile
: 输出一个文件,使用方法为 emitFile(name: string, content: Buffer|string, sourceMap: {...})。this.async
: 返回一个回调函数,用于异步执行。下面来看看less-loader
和style-loader
如何实现:
let less = require('less');
module.exports = function (source) {
const callback = this.async();
less.render(source, (err, result) => {
callback(err, result.css);
});
}
module.exports = function (source) {
let script = (`
let style = document.createElement("style");
style.innerText = ${JSON.stringify(source)};
document.head.appendChild(style);
`);
return script;
}
复制代码
webpack整个构建流程有许多钩子,开发者能够在指定的阶段加入本身的行为到webpack构建流程中。插件由如下构成:
整个webpack流程由compiler和compilation构成,compiler只会建立一次,compilation若是开起了watch文件变化,那么会屡次生成compilation. 那么这2个类下面生成了须要事件钩子
compiler hooks 文档compilation hooks 文档
写一个小插件,生成全部打包的文件列表(webpack4不推荐使用compiler.plugin来注册插件,webpack5将不支持):
classFileListPlugin{
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.emit.tap('FileListPlugin',function (compilation) {
let fileList = 'filelist:\n\n';
for (let filename in compilation.assets) {
fileList += ('- '+filename+'\n');
}
compilation.assets['filelist.md']={
source() {
return fileList;
},
size() {
return fileList.length
}
}
});
}
}
module.exports = FileListPlugin;
复制代码
都读在这里了,还不点个赞吗。
谢谢你阅读个人文章