本文也是屡次学习webpack积累下来的知识点,一直在云笔记里。javascript
webpack构建流程
从启动webpack构建到输出结果经历了一系列过程,它们是:css
须要注意的是,在构建生命周期中有一系列插件在合适的时机作了合适的事情,好比UglifyJsPlugin会在loader转换递归完后对结果再使用UglifyJs压缩覆盖以前的结果html
配置webpack就是在配置一个node的模块module.exports用于webpack来读取vue
webpack的优势:java
webapck的缺点:node
Rollupreact
webpack-dev-server是一个启动服务的程序,能够自动去,读取webpack.config.js 的文件。执行webpack-dev-server来启动,就能够来自动刷新页面,内部的原理是express的启服务,用webSocket来通知浏览器。jquery
module.exports = { context: path.resolve(__dirname, './app'), entry:'./main.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, './dist') } }
也能够动态的配置,就是返回函数,能够同步也能够异步webpack
同步 entry:()=>{ return { a: './pages/a', b:'./pages/a' } }, 异步 entry:new Promise((resolve)=>{ resolve({ a: '/a.js', b:'/b.js' }) }),
output是一个对象,里面有配置项git
能够取一下值:
rules: [{ test: '/\.js$/',//命中文件 use: ['babel-loader?cacheDirectory'],//指定loader 解析是从后向前 include: path.resolve(__dirname, 'src'),//包括 exclude: path.resolve(__dirname, 'node_modules')//排除 },{ test: /.vue$/, use: ['vue-loader'] }] vue须要使用vue-loader
noParse: /jquery|chartjs/ //通常是正则 被忽略的文件不该该有import/requie/define等
resolve: { alias: { components : './src/components' }, mainFields: ['jsnext:main', 'browser', 'main'],//jsnext 是支持ES6的模式进入 extension: ['.ts','.js','.json'],//后缀列表 //延长;延期 modules: ['./src/components', 'node_modules'],//指定本地解析 import 'button' enforceExtension: true,//开启必须带后缀 },
devServer:{ hot: true,//启用 webpack 的模块热替换特性 inline: true,//fasle是 iframe 模式 historyApiFallback: true,//spa history的模式, 任何请求都会返回index.html 也可使用重写rewrites来详细配置 contentBase:[path.join(__dirname, "public"), path.join(__dirname, "assets")],//提供静态文件 string fales关闭 array host:'0.0.0.0',//服务器外部可访问 port: '9090',//端口 disableHostCheck: true,//关闭host,devServer默认是接受本地请求配合host使用 https:true,//开启https或者本身导入证书 compress: true,//默认false开启gzip压缩 压缩 open: true,//打开浏览器 proxy:{ // credentials 证书 设置成include,表示容许跨越传递cookie "/api": "http://localhost:3000" } },
target:'web',//构建到的环境好比node web webworker等 devtool:'source-map',// 能够设置为false watch:true,//监听文件,默认是关闭的devserer默认打开 externals:{ jquery: 'jQuery' }, resolveLoader:{ //加载本地loader },
file-loader 将js和css中的图片等替换成正确的地址,输出在文件中
url-loader 能够将文件的内容通过base64编码后注入js或者css中,可是要限制大小
{ exclude: [ /\.(js|jsx)(\?.*)?$/, /\.(css|scss)$/, /\.json$/, /\.bmp$/, /\.jpe?g$/, /\.png$/, ], loader: require.resolve('file-loader'), options: { name: 'static/media/[name].[hash:8].[ext]', }, }, { test: [/\.bmp$/, /\.jpe?g$/, /\.png$/], loader: require.resolve('url-loader'), options: { limit: 10000, name: 'static/media/[name].[hash:8].[ext]', }, },
raw-loader 和 svg-inline-loader 能够把svg内嵌到网页,把svg当图片用,能够用上面的。
以方便在浏览器经过代码调试
devtool有不少的取值,由一下6个关键字随意组合而成
module:来时loader的source map 被简单处理成每行一个模块
source-map: 原始代码 最好的sourcemap质量有完整的结果,可是会很慢
eval和.map文件都是sourcemap实现的不一样方式,虽然大部分sourcemap的实现是经过产生.map文件, 但并不表示只能经过.map文件实现。下面是eval模式后产生的模块代码
cheap关键字的配置中只有行内,列信息指的是代码的不包含原始代码的列信息
npm 能够运行node__modules里安装的程序 能够缩短命令
javascript 用eslint 用.eslintrc json 文件配置 eslint-loader enforce: 'pre'
ts 用TSlint tslint.json 配置 tslint-loader
css stylelint 用.stylelintrc 配置 StyleLintPlugin 插件来处理
代码检测会变慢构建速度,能够配置IDE来检测,同时配置git Hook在代码提交时检测
var config = require('../config') var compiler = webpack(webpackConfig)
调用compiler的watch能够监听文件变化
require('webpack-dev-middleware')是express的一个插件
var app = express() var compiler = webpack(webpackConfig) var devMiddleware = require('webpack-dev-middleware')(compiler, { publicPath: webpackConfig.output.publicPath, stats: { colors: true, chunks: false } })
可是不支持hot模式须要require('webpack-hot-middleware')
app.use(hotMiddleware)
代理使用
var proxyMiddleware = require('http-proxy-middleware')
modules: [path.resolve(__dirname, 'node_modules')]
alias: { react: path.resolve(__dirname, './node_modules/react/dist/react.min.js') },
noParse: /jquery|chartjs/ //通常是正则 被忽略的文件不该该有import/require/define等
工程就把 react react-dom prop-types classnames mobx mobx-react lodash moment polyfill 等打进来 可使用 [npm version]_dll.js 用 npm version 的话只要 version 一改变咱们会从新打包,好比升级了 react ,咱们就会 version +,就会从新打包。
先建立一个webpack.config.dll.js
执行webpack --config ./webpack.config.dll.js把须要dll的文件输出到dist/dll
const webpack = require('webpack'); const path = require('path'); const {version} = require('./package.json'); module.exports = { entry: { 'react': [ 'react', 'react-dom', 'prop-types', 'classnames', 'lodash', 'moment' ] }, output: { path: path.join(__dirname, 'dist/dll'), filename: `[name].${version}.js`, library: 'dll_[name]', publicPath: '/dist/dll/' }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, 'dist/dll/', '[name].manifest.json'), name: 'dll_[name]' }) ] };
在webapck配置文件中使用
new webpack.DllReferencePlugin({ context: __dirname, // 在这里引入 manifest 文件 manifest: require('./dist/dll/react.manifest.json') }), //把js插入到html文件中 new AddAssetHtmlPlugin({ filepath: require.resolve(`./dist/dll/${getDLLFileName()}`), outputPath: 'dll', includeSourcemap: false, hash: true, publicPath: '/dist/dll/' })
原理就是提早构建,缓存起来,用window的全局变量来取。
{ test: /\.(js|jsx)$/,//命中文件 use: 'happypack/loader?id=js',//指定loader include: path.resolve(__dirname, 'src'),//包括 exclude: path.resolve(__dirname, 'node_modules')//排除 }, { test: /\.(css|scss)$/, loader: 'happypack/loader?id=css' }
new HappyPack({ id: 'js', threadPool: happyThreadPool, loaders: [{ path: 'babel-loader', query: { cacheDirectory: true } }] }), new HappyPack({ id: 'css', threadPool: happyThreadPool, loaders: ['style-loader','css-loader', 'sass-loader'] }),
原理:happypack 的原理是让loader能够多进程去处理文件,css和js,图片和文件支持很差
Devtool 使用 开发用cheap-module-eval-source-map 那个最快
区分环境
if (isDev) { config.plugins.push(new webpack.NamedModulesPlugin()); //显示模块更新名字 config.plugins.push(new webpack.HotModuleReplacementPlugin()); //hot更新插件 config.devServer = { hot: true, contentBase: './', historyApiFallback: { index: "/build/index.html" }, publicPath: '/build/', host: '0.0.0.0' }; config.devtool = 'eval'; } else { config.plugins.push(new webpack.optimize.UglifyJsPlugin({ // 开启压缩 cache: true, parallel: true, compress: { warning: fasle,// 是否删除一个警告信息fasle删除 drop_console: true,//删除console collapse_vars: true,是否内嵌虽然已定义可是只用到一次的变量。 reduce_vars: true ,提取屡次的变量 }, output: { comments: false,//是否删除注释,默认是不删除,设置为false,删除全部的注释 beautify: fasle, //保留空格和制表符 建议false关闭,最紧凑的输出 } })); config.devtool = '#source-map'; }
八、压缩css 在css-loader开启minimize选项 和 提取css到单独的文件
{ test: /\.(css|less)$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'happypack/loader?id=css' }) },
new ExtractTextPlugin('css/[name].[contenthash:8].css')
九、在webapck中接入cdn
十、Tree Shaking 去除重复代码 ,webapck没有程序流分析,避免不了babel产生的反作用
十一、以模块化来引入
// 原来的引入方式 import {debounce} from 'lodash'; //按模块化的引入方式 import debounce from 'lodash/debounce';
十二、使用异步的模块加载
require.ensure来设置哪些模块须要异步加载,webpack会将它打包到一个独立的chunk中
$('.bg-input').click(() => { console.log('clicked, loading async.js') require.ensure([], require => { require('./components/async2').log(); require('./components/async1').log(); console.log('loading async.js done'); }); });
import(*) 是新的按需加载,在react-router4中不能使用require.ensure要使用getAsyncComponent函数
component = { getAsyncComponent(() => { import('./pages/login') }) }
1三、Scope Hoisting 做用域提高 webpack3的新功能
使用ModuleConcatenationPlugin插件来加快JS执行速度
这是webpack3的新特性(Scope Hoisting),实际上是借鉴了Rollup打包工具来的,它将一些有联系的模块,放到一个闭包函数里面去,经过减小闭包函数数量从而加快JS的执行速度
new webpack.optimize.ModuleConcatenationPlugin({ })
原理:原理其实很简单,分析模块之间的依赖关系,尽量将被打散的模块合并到一个函数中,前提是不能形成代码冗余,源码必须采用es6语句。
1四、提取公共代码
使用CommonsChunkPlugin提取公共的模块,能够减小文件体积,也有助于浏览器层的文件缓存,仍是比较推荐的,那个是在最后打包的时候用的。
// 提取公共模块文件 new webpack.optimize.CommonsChunkPlugin({ chunks: ['home', 'detail'], // 开发环境下须要使用热更新替换,而此时common用chunkhash会出错,能够直接不用hash filename: '[name].js' + (isProduction ? '?[chunkhash:8]' : ''), name: 'common' }), // 切合公共模块的提取规则,有时后你须要明确指定默认放到公共文件的模块 // 文件入口配置 entry: { home: './src/js/home', detail: './src/js/detail', // 提取jquery入公共文件 common: ['jquery', 'react', 'react-dom'] }, entry: {index:'./src/index.js',vendor: ['react','react-dom','react-router']}, new webpack.optimize.CommonsChunkPlugin({ name: "vendor", filename: "vendor.js", }),
1五、prepack
利用抽象语法树(AST)来分析源码,过来问题大大,不建议使用
const PrepackWebpackPlugin = require('prepack-webapck-plugin').default; module.exports = { plugins: [ new PrepackWebpackPlugin() ] }
1六、可视化的输出分析 Analyse webpack-bundle-analyzer
用 Webpack 构建接入 Service Workers 的离线应用要解决的关键问题在于如何生成上面提到的 sw.js 文件, 而且sw.js文件中的 cacheFileList 变量,表明须要被缓存文件的 URL 列表,须要根据输出文件列表所对应的 URL 来决定,而不是像上面那样写成静态值。
假如构建输出的文件目录结构为:
├── app_4c3e186f.js ├── app_7cc98ad0.css └── index.html
那么 sw.js 文件中 cacheFileList 的值应该是:
var cacheFileList = [ '/index.html', 'app_4c3e186f.js', 'app_7cc98ad0.css' ];
Webpack 没有原生功能能完成以上要求,幸亏庞大的社区中已经有人为咱们作好了一个插件 serviceworker-webpack-plugin 能够方便的解决以上问题。 使用该插件后的 Webpack 配置以下:
const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const { WebPlugin } = require('web-webpack-plugin'); const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin'); module.exports = { entry: { app: './main.js'// Chunk app 的 JS 执行入口文件 }, output: { filename: '[name].js', publicPath: '', }, module: { rules: [ { test: /\.css/,// 增长对 CSS 文件的支持 // 提取出 Chunk 中的 CSS 代码到单独的文件中 use: ExtractTextPlugin.extract({ use: ['css-loader'] // 压缩 CSS 代码 }), }, ] }, plugins: [ // 一个 WebPlugin 对应一个 HTML 文件 new WebPlugin({ template: './template.html', // HTML 模版文件所在的文件路径 filename: 'index.html' // 输出的 HTML 的文件名称 }), new ExtractTextPlugin({ filename: `[name].css`,// 给输出的 CSS 文件名称加上 Hash 值 }), new ServiceWorkerWebpackPlugin({ // 自定义的 sw.js 文件所在路径 // ServiceWorkerWebpackPlugin 会把文件列表注入到生成的 sw.js 中 entry: path.join(__dirname, 'sw.js'), }), ], devServer: { // Service Workers 依赖 HTTPS,使用 DevServer 提供的 HTTPS 功能。 https: true, } };
├── pages │ ├── index │ │ ├── index.css // 该页面单独须要的 CSS 样式 │ │ └── index.js // 该页面的入口文件 │ └── login │ ├── index.css │ └── index.js ├── common.css // 全部页面都须要的公共 CSS 样式 ├── google_analytics.js ├── template.html └── webpack.config.js
AutoWebPlugin 强制性的规定了项目部分的目录结构,在pages下,每个文件夹就是一个目录。经过插件自动生成手动须要配置的两个html插件。
const { AutoWebPlugin } = require('web-webpack-plugin'); // 使用本文的主角 AutoWebPlugin,自动寻找 pages 目录下的全部目录,把每个目录当作一个单页应用 const autoWebPlugin = new AutoWebPlugin('pages', { template: './template.html', // HTML 模版文件所在的文件路径 postEntrys: ['./common.css'],// 全部页面都依赖这份通用的 CSS 样式文件 // 提取出全部页面公共的代码 commonsChunk: { name: 'common',// 提取出公共代码 Chunk 的名称 }, }); module.exports = { // AutoWebPlugin 会为寻找到的全部单页应用,生成对应的入口配置, // autoWebPlugin.entry 方法能够获取到全部由 autoWebPlugin 生成的入口配置 entry: autoWebPlugin.entry({ // 这里能够加入你额外须要的 Chunk 入口 }), plugins: [ autoWebPlugin, ], };
template.html 模版文件以下:
<html> <head> <meta charset="UTF-8"> <!--在这注入该页面所依赖但没有手动导入的 CSS--> <!--STYLE--> <!--注入 google_analytics 中的 JS 代码--> <script src="./google_analytics.js?_inline"></script> <!--异步加载 Disqus 评论--> <script src="https://dive-into-webpack.disqus.com/embed.js" async></script> </head> <body> <div id="app"></div> <!--在这注入该页面所依赖但没有手动导入的 JavaScript--> <!--SCRIPT--> <!--Disqus 评论容器--> <div id="disqus_thread"></div> </body> </html>
和 在模版中生成
// 引入插件 const HTMLWebpackPlugin = require("html-webpack-plugin"); // 引入多页面文件列表 const { HTMLDirs } = require("./config"); // 经过 html-webpack-plugin 生成的 HTML 集合 let HTMLPlugins = []; // 入口文件集合 let Entries = {} // 生成多页面的集合 HTMLDirs.forEach((page) => { const htmlPlugin = new HTMLWebpackPlugin({ filename: `${page}.html`, template: path.resolve(__dirname, `../app/html/${page}.html`), chunks: [page, 'commons'], }); HTMLPlugins.push(htmlPlugin); Entries[page] = path.resolve(__dirname, `../app/js/${page}.js`); })
构建服务端渲染
服务端渲染的代码要运行在nodejs环境,和浏览器不一样的是,服务端渲染代码须要采用commonjs规范同时不该该包含除js以外的文件好比css。webpack配置以下:
module.exports = { target: 'node', entry: { 'server_render': './src/server_render', }, output: { filename: './dist/server/[name].js', libraryTarget: 'commonjs2', }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', }, { test: /\.(scss|css|pdf)$/, loader: 'ignore-loader', }, ] }, };
其中几个关键的地方在于:
target: 'node' 指明构建出的代码是要运行在node环境里
libraryTarget: 'commonjs2' 指明输出的代码要是commonjs规范
{test: /.(scss|css|pdf)$/,loader: 'ignore-loader'} 是为了防止不能在node里执行服务端渲染也用不上的文件被打包进去。
若是你的扩展是想对一个个单独的文件进行转换那么就编写loader剩下的都是plugin
其中对文件进行转换能够是像:
编写loader很是简单,以comment-require-loader为例:
module.exports = function (content) { return replace(content); };
loader的入口须要导出一个函数,这个函数要干的事情就是转换一个文件的内容。
函数接收的参数content是一个文件在转换前的字符串形式内容,须要返回一个新的字符串形式内容做为转换后的结果,全部经过模块化倒入的文件都会通过loader。从这里能够看出loader只能处理一个个单独的文件而不能处理代码块
class EndWebpackPlugin {
constructor(doneCallback, failCallback) { this.doneCallback = doneCallback; this.failCallback = failCallback; } apply(compiler) { 汇编者; 编辑者; 编纂者 // 监听webpack生命周期里的事件,作相应的处理 compiler.plugin('done', (stats) => { this.doneCallback(stats); }); compiler.plugin('failed', (err) => { this.failCallback(err); }); }
}
module.exports = EndWebpackPlugin;
loader的入口须要导出一个class, 在new EndWebpackPlugin()的时候经过构造函数传入这个插件须要的参数,在webpack启动的时候会先实例化plugin再调用plugin的apply方法,插件须要在apply函数里监听webpack生命周期里的事件,作相应的处理。
webpack plugin 里有2个核心概念:
Compiler: 从webpack启动到推出只存在一个Compiler,Compiler存放着webpack配置
Compilation: 因为webpack的监听文件变化自动编译机制,Compilation表明一次编译。
Compiler 和 Compilation 都会广播一系列事件。
webpack生命周期里有很是多的事件能够在event-hooks和Compilation里查到
yaml-loader:加载 YAML 文件。
markdown-loader:把 Markdown 文件转换成 HTML。
coffee-loader:把 CoffeeScript 转换成 JavaScript。
stylus-loader:把 Stylus 代码转换成 CSS 代码。
coverjs-loader:计算测试覆盖率。
ui-component-loader:按需加载 UI 组件库,例如在使用 antd UI 组件库时,不会由于只用到了 Button 组件而打包进全部的组件。
ignore-plugin:用于忽略部分文件。
hot-module-replacement-plugin:开启模块热替换功能。
web-webpack-plugin:方便的为单页应用输出 HTML,比 html-webpack-plugin 好用。