webpack
立刻要出5了,彻底手写一个优化后的脚手架是不可或缺的技能。css
本文书写时间 2019年5月9日
, webpack
版本 4.30.0
最新版本html
要转载必须联系本人通过赞成才可转载 谢谢!node
杜绝5分钟
的技术,咱们先深刻原理再写配置,那会简单不少。react
实现需求:webpack
识别JSX
文件git
tree shaking
摇树优化 删除掉无用代码es6
PWA
功能,热刷新,安装后当即接管浏览器 离线后仍让能够访问网站 还能够在手机上添加网站到桌面使用github
CSS
模块化,不怕命名冲突web
小图片的base64
处理面试
文件后缀省掉jsx js json
等
实现懒加载,按需加载 , 代码分割
支持less sass stylus
等预处理
code spliting
优化首屏加载时间 不让一个文件体积过大
提取公共代码,打包成一个chunk
每一个chunk
有对应的chunkhash
,每一个文件有对应的contenthash
,方便浏览器区别缓存
图片压缩
CSS
压缩
增长CSS
前缀 兼容各类浏览器
对于各类不一样文件打包输出指定文件夹下
缓存babel
的编译结果,加快编译速度
每一个入口文件,对应一个chunk
,打包出来后对应一个文件 也是code spliting
删除HTML
文件的注释等无用内容
每次编译删除旧的打包代码
将CSS
文件单独抽取出来
等等....
webpack
中文官网的标语是 :让一切都变得简单
本质上,webpack
是一个现代 JavaScript
应用程序的静态模块打包器(module bundler
)。当 webpack
处理应用程序时,它会递归地构建一个依赖关系图(dependency graph
),其中包含应用程序须要的每一个模块,而后将全部这些模块打包成一个或多个 bundle
。
webpack v4.0.0
开始,能够不用引入一个配置文件。然而,webpack 仍然仍是高度可配置的。在开始前你须要先理解四个核心概念:
entry
)output
)loader
plugins
) 本文旨在给出这些概念的高度概述,同时提供具体概念的详尽相关用例。
让咱们一块儿来复习一下最基础的
Webpack
知识,若是你是高手,那么请直接忽略这些往下看吧....
入口
入口起点`(entry point)指示 webpack 应该使用哪一个模块,来做为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
每一个依赖项随即被处理,最后输出到称之为 bundles
的文件中,咱们将在下一章节详细讨论这个过程。
能够经过在 webpack
配置中配置entry
属性,来指定一个入口起点(或多个入口起点)。默认值为 ./src
。
接下来咱们看一个 entry
配置的最简单例子:
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js'
};
复制代码
entry: {
app: ['./src/index.js', './src/index.html'],
vendor: ['react']
},
entry: ['./src/index.js', './src/index.html'],
复制代码
HTML
文件,由于开发模式下热更新若是不设置入口为HTML
,那么更改了HTML
文件内容,是不会刷新页面的,须要手动刷新,因此这里给了入口HTML
文件,一个细节。出口(output)
webpack.config.js
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
}
};
复制代码
在上面的示例中,咱们经过 output.filename
和 output.path
属性,来告诉 webpack bundle
的名称,以及咱们想要 bundle
生成(emit
)到哪里。可能你想要了解在代码最上面导入的 path 模块是什么,它是一个Node.js
核心模块,用于操做文件路径。
loader
loader 让 webpack 可以去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 能够将全部类型的文件转换为 webpack 可以处理的有效模块,而后你就能够利用 webpack 的打包能力,对它们进行处理。
本质上,webpack loader 将全部类型的文件,转换为应用程序的依赖图(和最终的 bundle)能够直接引用的模块。
注意,loader 可以 import 导入任何类型的模块(例如 .css 文件),这是 webpack 特有的功能,其余打包程序或任务执行器的可能并不支持。咱们认为这种语言扩展是有很必要的,由于这可使开发人员建立出更准确的依赖关系图。
在更高层面,在 webpack 的配置中 loader 有两个目标:
test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
use 属性,表示进行转换时,应该使用哪一个 loader。
webpack.config.js
const path = require('path');
const config = {
output: {
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
}
};
module.exports = config;
复制代码
以上配置中,对一个单独的module
对象定义了 rules 属性,里面包含两个必须属性:test 和 use。这告诉 webpack 编译器(compiler
) 以下信息:
“嘿,webpack
编译器,当你碰到「在require()/import
语句中被解析为 '.txt'
的路径」时,在你对它打包以前,先使用 raw-loader
转换一下。”
重要的是要记得,在 webpack
配置中定义loader
时,要定义在module.rules
中,而不是 rules。然而,在定义错误时webpack
会给出严重的警告。为了使你受益于此,若是没有按照正确方式去作,webpack
会“给出严重的警告”
loader
还有更多咱们还没有提到的具体配置属性。
这里引用这位做者的优质文章内容,手写一个loader
和plugin
[手写一个loader和plugin][2]
高潮来了 ,
webpack
的编译原理 ,为何要先学学习原理? 由于你起码得知道你写的是干什么的!
webpack
打包原理
识别入口文件
经过逐层识别模块依赖。(Commonjs、amd
或者es6的import,webpack
都会对其进行分析。来获取代码的依赖)
webpack
作的就是分析代码。转换代码,编译代码,输出代码
最终造成打包后的代码
这些都是webpack
的一些基础知识,对于理解webpack
的工做机制颇有帮助。
什么是loader
?
loader
是文件加载器,可以加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一块儿打包到指定的文件中
处理一个文件可使用多个loader
,loader
的执行顺序是和自己的顺序是相反的,即最后一个loader
最早执行,第一个loader
最后执行。
第一个执行的loader
接收源文件内容做为参数,其余loader
接收前一个执行的loader
的返回值做为参数。最后执行的loader
会返回此模块的JavaScript
源码
在使用多个loader
处理文件时,若是要修改outputPath
输出目录,那么请在最上面的loader中options设置
什么是plugin?
在 Webpack
运行的生命周期中会广播出许多事件,Plugin
能够监听这些事件,在合适的时机经过 Webpack 提供的 API 改变输出结果。
plugin和loader
的区别是什么?
对于loader
,它就是一个转换器,将A文件进行编译造成B文件,这里操做的是文件,好比将A.scss或A.less转变为B.css,单纯的文件转换过程
plugin
是一个扩展器,它丰富了wepack
自己,针对是loader
结束后,webpack
打包的整个过程,它并不直接操做文件,而是基于事件机制工做,会监听webpack
打包过程当中的某些节点,执行普遍的任务。
webpack
的运行
webpack
启动后,在读取配置的过程当中会先执行 new MyPlugin(options)
初始化一个 MyPlugin 得到其实例。在初始化compiler 对象后,再调用myPlugin.apply(compiler)
给插件实例传入compiler
对象。插件实例在获取到 compiler
对象后,就能够经过 compiler.plugin
(事件名称, 回调函数) 监听到 Webpack
广播出来的事件。而且能够经过compiler
对象去操做webpack
compiler
是啥,compilation
又是啥?Compiler
对象包含了 Webpack 环境全部的的配置信息,包含 options,loaders,plugins
这些信息,这个对象在 Webpack 启动时候被实例化,它是全局惟一的,能够简单地把它理解为 Webpack
实例;Compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack
以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation
将被建立。Compilation
对象也提供了不少事件回调供插件作扩展。经过 Compilation
也能读取到Compiler
对象。Compiler
和 Compilation
的区别在于:Compiler
表明了整个Webpack
从启动到关闭的生命周期,而Compilation
只是表明了一次新的编译。事件流
webpack
经过Tapable
来组织这条复杂的生产线。webpack
的事件流机制保证了插件的有序性,使得整个系统扩展性很好。webpack
的事件流机制应用了观察者模式,和Node.js 中的 EventEmitter
很是类似。下面正式开始开发环境的配置:
chunk
vendor
,能够code spliting
,将这些公共的复用代码最终抽取成一个chunk
,单独打包出来HMTL
文件也热更新,须要加入·index.html
为入口文件entry: {
app: ['./src/index.js', './src/index.html'],
vendor: ['react'] //这里还能够加入redux react-redux better-scroll等公共代码
},
复制代码
output
出口
webpack
基于Node.js
环境运行,可使用Node.js
的API
,path
模块的resolve
方法JS
文件,加入contenthash
标示,让浏览器缓存文件,区别版本。output: {
filename: '[name].[contenthash:8].js',
path: resolve(__dirname, '../dist')
},
复制代码
mode: 'development'
模式选择,这里直接设置成开发模式,先从开发模式开始。resolve
解析配置,为了为了给全部文件后缀省掉 js jsx json
,加入配置resolve: {
extensions: [".js", ".json", ".jsx"]
}
复制代码
plugin
和html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin(),
复制代码
babel-loader
还有 解析JSX ES6
语法的 babel preset
@babel/preset-react
解析 jsx语法
@babel/preset-env
解析es6
语法@babel/plugin-syntax-dynamic-import
解析react-loadable
的import
按需加载,附带code spliting
功能{
test: /\.(js|jsx)$/,
use:
{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-react", ["@babel/preset-env", { "modules": false }]],
plugins: ["@babel/plugin-syntax-dynamic-import"]
},
}
},
复制代码
React
的按需加载,附带代码分割功能 ,每一个按需加载的组件打包后都会被单独分割成一个文件import React from 'react'
import loadable from 'react-loadable'
import Loading from '../loading'
const LoadableComponent = loadable({
loader: () => import('../Test/index.jsx'),
loading: Loading,
});
class Assets extends React.Component {
render() {
return (
<div>
<div>这即将按需加载</div>
<LoadableComponent />
</div>
)
}
}
export default Assets
复制代码
html-loader
识别html
文件{
test: /\.(html)$/,
loader: 'html-loader'
}
```
* 加入`eslint-loader`
复制代码
{
enforce:'pre',
test:/\.js$/,
exclude:/node_modules/,
include:resolve(__dirname,'/src/js'),
loader:'eslint-loader'
}
```
复制代码
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const WorkboxPlugin = require('workbox-webpack-plugin')
module.exports = {
entry: {
app: ['./src/index.js', './src/index.html'],
vendor: ['react', ]
},
output: {
filename: '[name].[hash:8].js',
path: resolve(__dirname, '../build')
},
module: {
rules: [
{
enforce:'pre',
test:/\.js$/,
exclude:/node_modules/,
include:resolve(__dirname,'/src/js'),
loader:'eslint-loader'
},
{
oneOf: [{
test: /\.(html)$/,
loader: 'html-loader'
},
{
test: /\.(js|jsx)$/,
use:
{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-react", ["@babel/preset-env", { "modules": false }]],
plugins: ["@babel/plugin-syntax-dynamic-import"]
},
}
},
{
test: /\.(less)$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader', options: {
modules: true,
localIdentName: '[local]--[hash:base64:5]'
}
},
{ loader: 'less-loader' }
]
}, {
test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[name].[hash:8].[ext]',
}
}, {
exclude: /\.(js|json|less|css|jsx)$/,
loader: 'file-loader',
options: {
outputPath: 'media/',
name: '[name].[hash].[ext]'
}
}
]
}]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), //打包时候能够看到文件名的插件
],
mode: 'development',
devServer: {
contentBase: '../build',
open: true,
port: 3000,
hot: true
},
resolve: {
extensions: [".js", ".json", ".jsx"]
}
}
复制代码
必须了解的
webpack
热更新原理 :
webpack
的热更新又称热替换(Hot Module Replacement
),缩写为HMR
。 这个机制能够作到不用刷新浏览器而将新变动的模块替换掉旧的模块。
首先要知道server端和client端都作了处理工做
第一步,在 webpack 的 watch
模式下,文件系统中某一个文件发生修改,webpack
监听到文件变化,根据配置文件对模块从新编译打包,并将打包后的代码经过简单的JavaScript
对象保存在内存中。
第二步是webpack-dev-server
和webpack
之间的接口交互,而在这一步,主要是 dev-server
的中间件 webpack-dev-middleware 和 webpack
之间的交互,webpack-dev-middleware
调用webpack
暴露的 API对代码变化进行监控,而且告诉webpack
,将代码打包到内存中。
第三步是 webpack-dev-server
对文件变化的一个监控,这一步不一样于第一步,并非监控代码变化从新打包。当咱们在配置文件中配置了devServer.watchContentBase
为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。
第四步也是webpack-dev-server
代码的工做,该步骤主要是经过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间创建一个 websocket 长链接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不一样的操做。固然服务端传递的最主要信息仍是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
webpack-dev-server/client
端并不可以请求更新的代码,也不会执行热更模块操做,而把这些工做又交回给了 webpack,webpack/hot/dev-server
的工做就是根据webpack-dev-server/client
传给它的信息以及dev-server
的配置决定是刷新浏览器呢仍是进行模块热更新。固然若是仅仅是刷新浏览器,也就没有后面那些步骤了。
HotModuleReplacement.runtime
是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash
值,它经过 JsonpMainTemplate.runtime
向 server 端发送 Ajax 请求,服务端返回一个json
,该 json
包含了全部要更新的模块的 hash 值,获取到更新列表后,该模块再次经过 jsonp 请求,获取到最新的模块代码。这就是上图中 七、八、9 步骤。
而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin
将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。
最后一步,当 HMR
失败后,回退到 live reload
操做,也就是进行浏览器刷新来获取最新打包代码。
参考文章 [webpack面试题-腾讯云][4]
正式开始生产环节:
WorkboxPlugin
, PWA
的插件
pwa
这个技术其实要想真正用好,仍是须要下点功夫,它有它的生命周期,以及它在浏览器中热更新带来的反作用等,须要认真研究。能够参考百度的lavas
框架发展历史~const WorkboxPlugin = require('workbox-webpack-plugin')
new WorkboxPlugin.GenerateSW({
clientsClaim: true, //让浏览器当即servece worker被接管
skipWaiting: true, // 更新sw文件后,当即插队到最前面
importWorkboxFrom: 'local',
include: [/\.js$/, /\.css$/, /\.html$/,/\.jpg/,/\.jpeg/,/\.svg/,/\.webp/,/\.png/],
}),
复制代码
const CleanWebpackPlugin = require('clean-webpack-plugin')
new CleanWebpackPlugin()
复制代码
code spliting
代码分割optimization: {
runtimeChunk:true, //设置为 true, 一个chunk打包后就是一个文件,一个chunk对应`一些js css 图片`等
splitChunks: {
chunks: 'all' // 默认 entry 的 chunk 不会被拆分, 配置成 all, 就能够了拆分了,一个入口`JS`,
//打包后就生成一个单独的文件
}
}
复制代码
{
test: /\.(jpg|jpeg|bmp|svg|png|webp|gif)$/,
use:[
{loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[name].[hash:8].[ext]',
outputPath:'/img'
}},
{
loader: 'img-loader',
options: {
plugins: [
require('imagemin-gifsicle')({
interlaced: false
}),
require('imagemin-mozjpeg')({
progressive: true,
arithmetic: false
}),
require('imagemin-pngquant')({
floyd: 0.5,
speed: 2
}),
require('imagemin-svgo')({
plugins: [
{ removeTitle: true },
{ convertPathData: false }
]
})
]
}
}
]
}
```
* 加入单独抽取`CSS`文件的`loader`和插件
复制代码
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
{
test: /\.(less)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader', options: {
modules: true,
localIdentName: '[local]--[hash:base64:5]'
}
},
{loader:'postcss-loader'},
{ loader: 'less-loader' }
]
}
new MiniCssExtractPlugin({
filename:'[name].[contenthash:8].css'
}),
复制代码
* 加入压缩`css`的插件
复制代码
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
new OptimizeCssAssetsWebpackPlugin({
cssProcessPluginOptions:{
preset:['default',{discardComments: {removeAll:true} }]
}
}),
复制代码
* 杀掉`html`一些没用的代码
复制代码
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
复制代码
}),
* 加入`file-loader` 把一些文件打包输出到固定的目录下
exclude: /\.(js|json|less|css|jsx)$/,
loader: 'file-loader',
options: {
outputPath: 'media/',
name: '[name].[contenthash:8].[ext]'
}
}
> 里面有一些注释可能不详细,代码都是本身一点点写,试过的,确定没用任何问题
* 须要的依赖
*
{
"name": "webpack",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"@babel/preset-react": "^7.0.0",
"autoprefixer": "^9.5.1",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^2.0.2",
"css-loader": "^2.1.1",
"eslint": "^5.16.0",
"eslint-loader": "^2.1.2",
"file-loader": "^3.0.1",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"imagemin": "^6.1.0",
"imagemin-gifsicle": "^6.0.1",
"imagemin-mozjpeg": "^8.0.0",
"imagemin-pngquant": "^7.0.0",
"imagemin-svgo": "^7.0.0",
"img-loader": "^3.0.1",
"less": "^3.9.0",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.6.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-loader": "^3.0.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-loadable": "^5.5.0",
"react-redux": "^7.0.3",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.3.1",
"workbox-webpack-plugin": "^4.3.1"
},
"scripts": {
"start": "webpack-dev-server --config ./config/webpack.dev.js",
"dev": "webpack-dev-server --config ./config/webpack.dev.js",
"build": "webpack --config ./config/webpack.prod.js "
},
"devDependencies": {
"@babel/plugin-syntax-dynamic-import": "^7.2.0"
}
}
# 整个项目和`webpack`配置的源码地址 : [源码地址啊 看得见吗亲][5]
# **路过的小伙伴麻烦点个赞给个star,写得好辛苦啊!!!!**
[1]: /img/bVbspk3
[2]: /img/bVbspEk
[3]: /img/bVbspE4
[4]: webpackjs.com/configuration/module/#rule-oneof
[5]: https://github.com/JinJieTan/React-webpack复制代码