webpack demos
Table of Contents
核心概念
Entry: 入口
Output: 输出结果
Module: 模块,webpack 中一切皆是模块
Loader: 模块转换器,用于把模块原内容按照需求转换成新内容
Plugin: 扩展插件,在 webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情
loader
Webpack loaders API
路径 webpack 中查找 loader 的路径
loader 的绝对路径
设置 loader 的别名
设置 loader 的查找模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const path = require ('path' )const resolve = (...dir ) => path.resolve(__dirname, ...dir)module .exports = { mode: 'development' , entry: resolve('src/index.js' ), output: { filename: 'build.js' , path: resolve('dist' ) }, resolveLoader: { modules: ['node_modules' , resolve('loaders' )], alias: { 'x-loader' : resolve('loaders/x-loader.js' ) } }, module : { rules: [ { test: /\.js$/ , use: resolve('loaders/x-loader.js' ) }, { test: /\.js$/ , use: ['1-loader' , '2-loader' , '3-loader' ] } ] } }
顺序
webpack 中 loader 的默认执行顺序是从下到上,从右向左执行enforce 调用的优先级(权重),调整调用顺序
顺序:前置(pre) > 普通(normal) > 行内(inline) > 后置(post)
1 2 3 4 5 6 import '!!inline-loader!./a.js'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 module .exports = { module : { rules: [ { test: /\.js$/ , use: resolve('loaders/x-loader.js' ) }, { test: /\.js$/ , use: ['1-loader' ], enforce: 'pre' }, { test: /\.js$/ , use: ['2-loader' ] }, { test: /\.js$/ , use: ['3-loader' ], enforce: 'post' } ] } }
Pitch loader
pitch 方法在 Loader 中便是从左到右执行的,并且可以通过 data 这个变量来进行 pitch 和 normal 之间传递。熔断功能
正常顺序:
1 2 3 4 5 6 7 |- a-loader `pitch` |- b-loader `pitch` |- c-loader `pitch` |- requested module is picked up as a dependency |- c-loader normal execution |- b-loader normal execution |- a-loader normal execution
设置 b-loader 的 pitch 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 module .exports = function (source ) { return source } module .exports.pitch = function (remainingRequest, precedingRequest, data ) { if (someCondition()) { return ( 'module.exports = require(' + JSON .stringify('-!' + remainingRequest) + ');' ) } }
设置之后的顺序:
1 2 3 |- a-loader `pitch` |- b-loader `pitch` returns a module |- a-loader normal execution
raw loader 默认的情况,原文件是以 UTF-8 String 的形式传入给 Loader,而在上面有提到的,module 可使用 buffer 的形式进行处理,针对这种情况,只需要设置 module.exports.raw = true; 这样内容将会以 raw Buffer 的形式传入到 loader 中了
1 2 3 4 5 module .exports = function (source ) { return source } module .exports.raw = true
loader context loader 中的上下文 this
data: pitch loader 中可以通过 data 让 pitch 和 normal module 进行数据共享。
query: 则能获取到 Loader 上附有的参数。 如 require(“./somg-loader?ls”); 通过 query 就可以得到 “ls” 了。
emitFile: emitFile 能够让开发者更方便的输出一个 file 文件,这是 webpack 特有的方法,使用的方法也很直接
1 2 3 4 module .exports = function (source ) { console .log(this ) return source }
awesome
name
description
link
html-loader
处理 html 中的资源引用
GitHub
url-loader
处理资源文件,image、video 等
GitHub
file-loader
处理资源文件,image、video 等
GitHub
babel-loader
es+ 转 es5
GitHub
plugin
plugin API webpack plugins
webpack 中的 plugin 必须是一个 class,且拥有 apply 方法
webpack 打包时会自动调用 plugin 实例的 apply 方法,并传递 compiler 参数
compiler 上有个 hooks 属性(钩子函数),plugin 就是基于钩子函数来做处理
server proxy 简单的代理请求到目标域
1 2 3 4 5 6 7 8 9 10 module .exports = { devServer: { proxy: { '/api' : { target: 'https://localhost' , pathRewrite: { '/api' : '' } } } } }
hook webpack 中使用 express server
webpack dev server 是基于 express 构建的,它提供一个钩子函数 before 在参数中将 express app 暴露出来,用户可以自定义添加路由,模拟数据等
1 2 3 4 5 6 7 8 9 module .exports = { devServer: { before(app) { app.get('/user' , (req, res) => { res.json({ name : 'hello' }) }) } } }
webpack-dev-middleware
webpack/webpack-dev-middleware
用户自定义的 server 中使用 webpack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const webpack = require ('webpack' )const middleware = require ('webpack-dev-middleware' )const config = require ('./webpack.config.js' )const compiler = webpack({ ...config }) const express = require ('express' )const app = express()app.use( middleware(compiler, { }) ) app.listen(3000 , () => console .log('Example app listening on port 3000!' ))
全局变量 expose-loader 通过 expose-loader 将 import 的模块暴露到全局 window 上
loader 中配置
1 2 3 4 5 6 7 8 9 10 11 12 module .exports = { module : { rules: [ { test: require .resolve('jquery' ), use: 'expose-loader?$' } ] } }
內联形式
1 import $ from 'expose-loader?$!jquery'
Webpack.ProvidePlugin 通过 new Webpack.ProvidePlugin(), 给每个模块文件直接注入一个模块,其他模块文件无需引用,直接调用
1 2 3 4 5 6 7 module .exports = { plugins: [ new Webpack.ProvidePlugin({ $: 'jquery' }) ] }
externals 通过 script 在 html 引用,webpack 中设置 externals 直接使用外部引用,不打包进项目
1 2 3 4 5 6 7 8 9 10 11 module .exports = { externals: { jquery: 'jQuery' } } import $ from 'jquery' $('.my-element' ).animate()
优化 module.noParse
module.noParse
不解析正则匹配的文件内部的 import, require, define 等
noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能。 原因是一些库例如 jQuery lodash 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义。
1 2 3 4 5 6 module .exports = { module : { noParse: /jquery|lodash/ } }
Webpack.IgnorePlugin
Webpack.IgnorePlugin webpack 自带的插件
比如 moment 库中的语言包很大,使用 IgnorePlugin 在 moment 中任何以 ‘./locale’ 结尾的 require 都将被忽略
1 2 3 4 new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/ , contextRegExp: /moment$/ })
用户需自行引入语言包
1 2 3 4 import moment from 'moment' import 'moment/locale/zh-cn' moment.locals('zh-cn' )
Webpack.DllPlugin
Webpack.DllPlugin
DllPlugin 是基于 Windows 动态链接库(dll)的思想被创作出来的。这个插件会把第三方库单独打包到一个文件中,这个文件就是一个单纯的依赖库。这个依赖库不会跟着你的业务代码一起被重新打包,只有当依赖自身发生版本变化时才会重新打包。
用 DllPlugin 处理文件,需要两步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 const path = require ('path' )const HtmlWebpackPlugin = require ('html-webpack-plugin' )const Webpack = require ('webpack' )const resolve = (...dir ) => path.resolve(__dirname, ...dir)module .exports = { mode: 'development' , entry: { home: './src/index.js' }, output: { filename: '[name].js' , path: resolve('dist' ) }, devServer: { port: 3000 , open: true , contentBase: './dist' }, module : { noParse: /jquery/ , rules: [ { test: /.css$/ , use: ['style-loader' , 'css-loader' ] }, { test: /.js$/ , exclude: /node_modules/ , include: resolve('src' ), use: { loader: 'babel-loader' , options: { presets: ['@babel/preset-env' , '@babel/preset-react' ] } } } ] }, plugins: [ new Webpack.DllReferencePlugin({ manifest: resolve('dist' , 'manifest.json' ) }), new Webpack.IgnorePlugin(/\.\/local/ , /moment/), new HtmlWebpackPlugin({ template: './src/index.html' , filename: 'index.html' }) ] }
happypack
多线程打包 - GitHub
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const HappyPack = require ('happypack' )exports.module = { rules: [ { test: /.js$/ , use: 'happypack/loader' , include: [ ], exclude: [ ] } ] } exports.plugins = [ new HappyPack({ loaders: ['babel-loader?presets[]=es2015' ] }) ]
Tree-Shaking webpack 自带 Tree-Shaking, scope hosting
1 2 3 4 5 6 7 8 const bar = 1 const foo = 2 const foobar = bar + fooconsole .log(foobar)console .log(3 )
基于 import/export 语法,Tree-Shaking 可以在编译的过程中获悉哪些模块并没有真正被使用,这些没用的代码,在最后打包的时候会被去除。适合于处理模块级别的代码,所以尽量使用 es6 的 import/export 语法。
SplitChunksPlugin
webpack - SplitChunksPlugin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 module .exports = { optimization: { splitChunks: { chunks: 'async' , minSize: 30000 , maxSize: 0 , minChunks: 1 , maxAsyncRequests: 5 , maxInitialRequests: 3 , automaticNameDelimiter: '~' , automaticNameMaxLength: 30 , name: true , cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/ , priority: -10 }, default : { minSize: 0 , minChunks: 2 , priority: -20 , reuseExistingChunk: true } } } } }
dynamic imports
import() 还在草案中,需要 @babel/plugin-syntax-dynamic-import 才能使用
通过 es6 的 import 实现按需加载,在使用 import() 分割代码后,你的浏览器并且要支持 Promise API 才能让代码正常运行, 因为 import() 返回一个 Promise,它依赖 Promise。对于不原生支持 Promise 的浏览器,你可以注入 Promise polyfill。
1 2 3 import ('./a' )import ('./b' )
HMR - Hot Module Replacement 模块热替换(HMR - Hot Module Replacement)是 webpack 提供的最有用的功能之一。它允许在运行时替换,添加,删除各种模块,而无需进行完全刷新重新加载整个页面
启用 HRM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 const path = require ('path' )const HtmlWebpackPlugin = require ('html-webpack-plugin' )const Webpack = require ('webpack' )const resolve = (...dir ) => path.resolve(__dirname, ...dir)module .exports = { mode: 'production' , entry: { index: './src/index.js' , other: './src/other.js' }, output: { filename: '[name].js' , path: resolve('dist' ) }, devServer: { hot: true , port: 3000 , open: true , contentBase: './dist' }, module : { rules: [ { test: /.js$/ , exclude: /node_modules/ , include: resolve('src' ), use: { loader: 'babel-loader' , options: { presets: ['@babel/preset-env' , '@babel/preset-react' ], plugins: ['@babel/plugin-syntax-dynamic-import' ] } } } ] }, plugins: [ new HtmlWebpackPlugin({ template: 'public/index.html' , name: 'index.html' }), new Webpack.NamedModulesPlugin(), new Webpack.HotModuleReplacementPlugin() ] }