webpack初识
1、什么是Webpack
Webpack可以看做是模块化打包工具(webpack出现,是为了解决前端模块化的问题):它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。在3.0出现后,Webpack还肩负起了优化项目的责任。
这段话有三个重点:
- 打包:可以把多个Javascript文件打包成一个文件,减少服务器压力和下载带宽。
- 转换(翻译):把拓展语言转换成为普通的JavaScript,让浏览器顺利运行。
- 优化:前端变的越来越复杂后,性能也会遇到问题,而WebPack也开始肩负起了优化和提升性能的责任。
1.1 前端模块化的优势: 1. (函数)作用域封装,避免全局作用域变量间的污染 2. 提高代码重用性 3. 降低耦合
2、为什要使用WebPack
如今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法:
a:模块化,让我们可以把复杂的程序细化为小的文件;
b:类似于TypeScript这种在JavaScript基础上拓展的开发语言,使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能转换为JavaScript文件使浏览器可以识别;
c:scss,less等CSS预处理器。
3. webpack打包过程
- 从入口文件(src/index.js)开始,分析整个应用的依赖树。
- 将每个依赖模块包装起来,放到一个数组(立即执行函数的参数)中等待调用。
- 实现模块加载(_webpack_require_)的方法,并把它放到模块执行环境中,确保模块之间可以相互调用。
- 把执行入口文件的逻辑放到一个函数表达式中,并立即执行这个函数。
(function (modules) { var installedModules = {}; function __webpack_require__ (moduleID) { // code } // ... __webpack_require__(0) // 加载入口模块 })([/* 2. modules array */])
entry
分离 应用程序(app) 和 第三方库(vendor) 入口。
从表面上看,这告诉我们 webpack 从 app.js
和 vendors.js
开始创建依赖图(dependency graph)。这些依赖图是彼此完全分离、互相独立的。常见于,只有一个入口起点(不包括 vendor)的单页应用程序(single page application)中.
1 // webpack.config.js 2 const config = { 3 entry: { 4 app: './src/app.js', 5 vendors: './src/vendors.js' 6 } 7 };
有时可以选择将项目的基础依赖(一定会用到的,如vue)摘出来,也加到entry中。参考:https://zhuanlan.zhihu.com/p/32093510
entry: { app: path.join(__dirname, 'main.js'), vendor: ['vue'] }
之后加载的类库可以使用commonsChunkPlugin添加(webpack4中 webpack.optimize.CommonsChunkPlugin 被 optimization.splitChunks 替代)
plugins: [ //...此前的代码 new webpack.HashedModuleIdsPlugin(), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: Infinity, }), new webpack.optimize.CommonsChunkPlugin({ name: 'common', minChunks: function(module) { return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, './node_modules') ) === 0 ) }, chunks: ['index'], }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor', 'common', 'index'] }) ]
(插件 HashedModuleIdsPlugin
,是用于保持模块引用的 module id
不变。而 CommonsChunkPlugin
则提取入口指定的依赖独立打包,minChunks: Infinity
的用意是让插件别管其他,就按照设置的数组提取文件就好。另一个chunk命名为 common
,指定它从入口 index.js
中抽取来自 node_modules
的依赖(可变依赖,区别于vendor)。最后就是抽取 webpack
运行时的函数及其模块标识组成 manifest
。)
多页面应用程序
下面的代码中,webpack 构建了3 个独立分离的依赖图。使用 optimization.splitChunks 可为每个页面间的应用程序共享代码创建 bundle。由于入口起点增多,多页应用能够复用入口起点之间的大量代码/模块。
1 // webpack.config.js 2 const config = { 3 entry: { 4 pageOne: './src/pageOne/index.js', 5 pageTwo: './src/pageTwo/index.js', 6 pageThree: './src/pageThree/index.js' 7 } 8 };
output
- path 目标输出目录的绝对路径。
- filename 用于输出文件的文件名。
A. 对于单个入口
起点,filename 会是一个静态名称。
filename: "bundle.js"
B. 然而,当通过多个入口起点(entry point)、代码拆分(code splitting)或各种插件(plugin)创建多个 bundle,应该使用以下一种替换方式,来赋予每个 bundle 一个唯一的名称。
- 使用入口名称:
filename: "[name].bundle.js"
- 使用内部 chunk id:
filename: "[id].bundle.js"
- 使用每次构建过程中,唯一的 hash 生成
filename: "[name].[hash].bundle.js"
- 使用基于每个 chunk 内容的 hash:
filename: "[name].[chunkhash].js"
- publicPath 按需加载或外部加载资源时的目录,可以是绝对路径或相对路径。
publicPath: "https://cdn.example.com/assets/", // CDN(总是 HTTPS 协议) publicPath: "//cdn.example.com/assets/", // CDN (协议相同) publicPath: "/assets/", // 相对于服务(server-relative) publicPath: "assets/", // 相对于 HTML 页面 publicPath: "../assets/", // 相对于 HTML 页面 publicPath: "", // 相对于 HTML 页面(目录相同)
- library 与libraryTarget配合使用
- libraryTarget 默认选项是
var.
output: { library: "MyLibrary" }
//如果生成的输出文件,是在 HTML 页面中作为一个 script 标签引入,则变量
MyLibrary
将与入口文件的返回值绑定。
单一入口起点
// webpack.config.js const config = { output: { filename: 'bundle.js', path: '/home/proj/public/assets' } }; module.exports = config;
多个入口起点
{ entry: { app: './src/app.js', search: './src/search.js' }, output: { filename: '[name].js', path: path.join(__dirname, '/dist') } } // 写入到硬盘:./dist/app.js, ./dist/search.js
mode(模式)分为两种:development和production
1 // webpack.config.js 2 3 const config = { 4 mode: 'production' 5 }; 6 module.exports = config;
选项
|
描述 |
development |
会将 process.env.NODE_ENV 的值设为 development 。启用 NamedChunksPlugin 和 NamedModulesPlugin 。 |
production |
会将 |
压缩输出:从 webpack 4 开始,可以通过 "mode"
配置选项设置为 "production",轻松实现压缩输出。
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: "production"
};
loader
webpack 自身只理解 JavaScript. loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后就可利用 webpack 的打包能力对它们进行处理。本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。
// webpack.config.js const path = require('path'); const config = { output: { filename: 'my-first-webpack.bundle.js' }, module: { rules: [ {
test: /\.txt$/,
use: 'raw-loader',
include: path.resolve(__dirname, "src)
}
]
}
};
module.exports = config;
{ test: /.css$/, oneOf: [ //当规则匹配时,只使用第一个匹配规则 { resourceQuery: /inline/, // foo.css?inline use: 'url-loader' }, { resourceQuery: /external/, // foo.css?external use: 'file-loader' } ] }
test
字段,用于标识出应该被对应的 loader 进行转换的某个或某些文件。use
字段,表示进行转换时,应该使用哪个 loader。include
字段,仅将 loader 模块应用在实际需要用其转换的路径下。
在 webpack 配置中定义 loader 时,要定义在 module.rules 中,而不是 rules。
rules条件有两种输入值:
-
resource:请求文件的绝对路径。它已经根据
resolve
规则解析。 -
issuer: 被请求资源(requested the resource)的模块文件的绝对路径。是导入时的位置。
例如: 从 app.js
导入 './style.css'
,resource 是 /path/to/style.css
. issuer 是 /path/to/app.js
。
在规则中,属性 test
, include
, exclude
和 resource
对 resource 匹配,而属性 issuer
对 issuer 匹配。
plugins(插件)
功能强大,包括打包优化、压缩、重新定义环境中的变量等。
想要使用一个插件,你只需要 require()
它,然后把它添加到 plugins
数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new
操作符来创建它的一个实例。
const webpack = require('webpack'); // 用于访问内置插件 // 导入非 webpack 自带默认插件 // 通过 npm 安装 var ExtractTextPlugin = require('extract-text-webpack-plugin'); var DashboardPlugin = require('webpack-dashboard/plugin'); const config = { module: { rules: [ { test: /\.txt$/, use: 'raw-loader' } ] }, plugins: [
// UglifyJSPlugin被optimization.minimize替代
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false, drop_console: false, } }),
// extractTextPlugin被替代(见本文最后) new ExtractTextPlugin({ filename: 'build.min.css', allChunks: true, }), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // 编译时(compile time)插件:创建编译时可配置的全局常量 new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"', }), // webpack-dev-server 强化插件 new DashboardPlugin(), new webpack.HotModuleReplacementPlugin(), ] }; module.exports = config;
- ProvidePlugin:自动加载模块,而不必使用import或者require。
// webpack.config.js
new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' });
之后可在我们的代码中随意使用$和jQery,不需要引入。
devServer https://www.webpackjs.com/configuration/dev-server/
allowedHosts 允许访问devServer的白名单。
allowedHosts: [ 'host.com', 'subdomain.host.com', 'subdomain2.host.com', 'host2.com' ]
before函数 优先于其他,进行handler定义。
before(app){ app.get('/some/path', function(req, res) { res.json({ custom: 'response' }); }); }
compress 一切服务都使用gzip压缩。
compress: true
contentBase 提供静态文件时的目录(推荐使用绝对路径)。
默认情况下,将使用当前工作目录作为提供内容的目录,但可修改为其他目录:
contentBase: path.join(__dirname, "public")
也可以从多个目录提供内容:
contentBase: [path.join(__dirname, "public"), path.join(__dirname, "assets")]
watchContentBase 文件改变将触发整个页面的重载。
lazy 是否开启惰性模式。当启用 lazy
时,dev-server 只有在请求时才编译包(bundle)。这意味着 webpack 不会监视任何文件改动。
filename 在惰性模式下,使用filename可以减少编译。
lazy: true, filename: "bundle.js"
只有在请求 /bundle.js
时候,才会编译 bundle。
header 在所有响应中添加首部内容。
headers: { "X-Custom-Foo": "bar" }
host 默认是 localhost
。如果你希望服务器外部可访问,指定如下:
host: "0.0.0.0"
port 指定要监听请求的端口号.
port:8080
hot 是否启用模块热替换的特性。
https devServer是否提供https服务。默认情况下,dev-server 通过 HTTP 提供服务。
proxy 代理。如果在 localhost:3000
上有后端服务的话,你可以这样启用代理:
proxy: { "/api": "http://localhost:3000" }
请求到 /api/users
现在会被代理到请求 http://localhost:3000/api/users.
如果不想始终传递 /api
,则需要重写路径:
proxy: { "/api": { target: "http://localhost:3000", pathRewrite: {"^/api" : ""} } }
若想要代理多个接口到同一后端,可通过context后连接一个数组。
proxy: [{ context: ["/auth", "/api"], target: "http://localhost:3000", }]
devtool:
开发环境
eval
- 每个模块都使用 eval()
执行,并且都有 //@ sourceURL
。此选项会非常快地构建。主要缺点是,由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数.
eval-source-map
- 每个模块使用 eval()
执行,并且 source map 转换为 DataUrl 后添加到 eval()
中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。
cheap-eval-source-map
- (类似 eval-source-map)
每个模块使用 eval()
执行。这是 "cheap(低开销)" 的 source map,因为它没有生成列映射(column mapping),只是映射行数。它会忽略源自 loader 的 source map,并且仅显示转译后的代码,就像 eval
devtool。
cheap-module-eval-source-map
- 类似 cheap-eval-source-map
,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。
生产环境
(none)
(省略 devtool
选项) - 不生成 source map。这是一个不错的选择。
source-map
- 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它。
应将服务器配置为,不允许普通用户访问 source map 文件!
hidden-source-map
- 与 source-map
相同,但不会为 bundle 添加引用注释。如果你只想 source map 映射那些源自错误报告的错误堆栈跟踪信息,但不想为浏览器开发工具暴露你的 source map,这个选项会很有用。
你不应将 source map 文件部署到 web 服务器。而是只将其用于错误报告工具。
nosources-source-map
- 创建的 source map 不包含 sourcesContent(源代码内容)
。它可以用来映射客户端上的堆栈跟踪,而无须暴露所有的源代码。你可以将 source map 文件部署到 web 服务器。
注意问题:
1.混合使用ES2015,AMD CommonJS
你可以自由混合使用三种模块类型(甚至在同一个文件中)。在这个情况中 webpack 的行为和 babel一致。
// ES2015 模块调用 CommonJS import fs from "fs"; // module.exports 映射到 default import { readFileSync } from "fs"; // 从返回对象(returned object+)中读取命名的导出方法(named exports) typeof fs.readFileSync === "function"; typeof readFileSync === "function";
需要让 Babel 不解析这些模块符号,从而让 webpack 可以使用它们。可以通过设置如下配置到 .babelrc
{ "presets": [ ["@babel/preset-env", {"modules": false}], ] }
2.只在production环境下才会用到的工具(在development时应避免)
UglifyJsPlugin(webpack4中,UglifyJsPlugin 被内置,只需
optimization.minimize:true,且在
production mode下面自动为true)ExtractTextPlugin(webpack中 使用mini-css-extract-plugin代替)
[hash]
/[chunkhash]
AggressiveSplittingPlugin
AggressiveMergingPlugin
ModuleConcatenationPlugin
resolve
以下建议可以提高解析速度
- 尽量减少
resolve.modules
,resolve.extensions
,resolve.mainFiles
,resolve.descriptionFiles
中类目的数量,因为他们会增加文件系统调用的次数。 - 如果不使用 symlinks ,可以设置
resolve.symlinks: false.
- 如果使用自定义解析 plugins ,并且没有指定 context 信息,可以设置
resolve.cacheWithContext: false
。
webpack 5
1. scalability
持久缓存
webpack工作原理:webpack读取入口文件(entry),然后递归查找所依赖的模块(module),构建成一个“依赖图”,然后根据配置中的加载器(loader)和打包策略来对模块进行编译。如果中间有文件发生变化了,上面所述的整个递归遍历流程会重新再进行一次。
webpack 5利用持久缓存优化了整个流程,当检测到某个文件变化时,依照“依赖图”,只对修改过的文件进行编译,从而大幅提高了编译速度。
引入了更多新东西
webpack 5 要求node的最低版本为 Node 8。
另外,webpack 5还引入了webAssembly、Hashing、多线程、还有workers。
NodeJS的polyfill脚本被移除.