webpack
立刻要出5了,彻底手写一个优化后的脚手架是不可或缺的技能。
2019年5月9日
, webpack
版本 4.30.0
最新版本《前端进阶》
之后都是高赞高质量文章5分钟
的技术,咱们先深刻原理再写配置,那会简单不少。我这套代码,在开发环境中性能不是完美的,可是构建速度打包生产环境代码是极快
的,请你必定要去看个人git
仓库,如今已经加入了项目实践
,也在里面,能够的话给个star
哦
JSX
文件tree shaking
摇树优化 删除掉无用代码async / await
和 箭头函数PWA
功能,热刷新,安装后当即接管浏览器 离线后仍让能够访问网站 还能够在手机上添加网站到桌面使用preload
预加载资源 prefetch
按需请求资源CSS
模块化,不怕命名冲突base64
处理jsx js json
等less sass stylus
等预处理code spliting
优化首屏加载时间 不让一个文件体积过大dns-prefetch
和preload
预请求必要的资源,加快首屏渲染。prerender
,极大加快首屏渲染速度。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
)本文旨在给出这些概念的高度概述,同时提供具体概念的详尽相关用例。
css
让咱们一块儿来复习一下最基础的
Webpack
知识,若是你是高手,那么请直接忽略这些往下看吧....
入口html
bundles
的文件中,咱们将在下一章节详细讨论这个过程。webpack
配置中配置 entry
属性,来指定一个入口起点(或多个入口起点)。默认值为 ./src
。接下来咱们看一个 entry
配置的最简单例子:前端
webpack.config.js module.exports = { entry: './path/to/my/entry/file.js' };
入口能够是一个对象,也能够是一个纯数组node
entry: { app: ['./src/index.js', './src/index.html'], vendor: ['react'] }, entry: ['./src/index.js', './src/index.html'],
HTML
文件,由于开发模式下热更新若是不设置入口为HTML
,那么更改了HTML
文件内容,是不会刷新页面的,须要手动刷新,因此这里给了入口HTML
文件,一个细节。出口(output)react
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
核心模块,用于操做文件路径。webpack
loader
git
use 属性,表示进行转换时,应该使用哪一个 loader。es6
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 webpack
的编译原理 ,为何要先学学习原理? 由于你起码得知道你写的是干什么的!webpack
打包原理github
Commonjs、amd
或者es6的import,webpack
都会对其进行分析。来获取代码的依赖)webpack
作的就是分析代码。转换代码,编译代码,输出代码webpack
的一些基础知识,对于理解webpack
的工做机制颇有帮助。什么是loader
?web
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(),
optimization: { runtimeChunk: true, splitChunks: { chunks: 'all' } }
加入 babel-loader
还有 解析JSX ES6
语法的 babel preset
@babel/preset-react
解析 jsx语法
@babel/preset-env
解析es6
语法@babel/plugin-syntax-dynamic-import
解析react-loadable
的import
按需加载,附带code spliting
功能 ["import", { libraryName: "antd-mobile", style: true }],
Antd-mobile的按需加载{ loader: 'babel-loader', options: { //jsx语法 presets: ["@babel/preset-react", //tree shaking 按需加载babel-polifill ["@babel/preset-env", { "modules": false, "useBuiltIns": "false", "corejs": 2 }]], plugins: [ //支持import 懒加载 "@babel/plugin-syntax-dynamic-import", //andt-mobile按需加载 true是less,若是不用less style的值能够写'css' ["import", { libraryName: "antd-mobile", style: true }], //识别class组件 ["@babel/plugin-proposal-class-properties", { "loose": true }], ], cacheDirectory: true }, }
thread-loader
,在babel
首次编译后开启多线程const os = require('os') { loader: 'thread-loader', options: { workers: os.cpus().length } }
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' }
git
仓库里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 步骤。HotModulePlugin
将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。HMR
失败后,回退到 live reload
操做,也就是进行浏览器刷新来获取最新打包代码。加入 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`, //打包后就生成一个单独的文件 } }
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, } }),
{ 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 } ] }) ] } } ] }
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
配置的源码地址 已经更新 : 源码地址啊 看得见吗亲