共计 9365 个字符,预计需要花费 24 分钟才能阅读完成。
针对 webpack,是大家(前端开发)在日常的开发中都会遇见的,通过书写的形式输入,学习到的对于前端工程化的小知识点的总结和学习,造成本人的常识体系
概念
webpack 官网定义:
webpack 是一个古代 JavaScript 应用程序的动态模块打包器(module bundler)。当 webpack 解决应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中蕴含应用程序须要的每个模块,而后将所有这些模块打包成一个或多个 bundle。
在开始理解 webpack 配置前,首先须要了解四个 外围概念:
- 入口(entry):webpack 应该应用哪个模块,来作为构建其外部依赖图的开始。
- 输入(output):webpack 在哪里输入它所创立的 bundles,以及如何命名这些文件。
- loader:可能去解决那些非 JavaScript 文件(webpack 本身只了解 JavaScript)。
- 插件(plugins):用于执行范畴更广的工作。
装置及构建
// npm 装置
npm install webpack webpack-cli -g
// yarn 装置
yarn global add webpack webpack-cli
装置好后,能够在不实用配置文件的办法,间接对文件进行打包:webpack <entry> [<entry>] -o <output>
新建一个我的项目,就一个入口文件,测试 webpack 打包:
运行打包命令:webpack index.js
在这里咱们会看见一个 WARNING
的信息,这是因为没有设置 mode
,咱们只须要加一个参数-p
即可:
webpack -p index
这样默认会生成一个 dist
文件夹,外面有个 main.js
文件:
有了入口文件咱们还须要通过命令行定义一下输出门路dist/bundle.js
:
webpack -p index.js -o dist/bundle.js
webpack 配置文件
命令行的打包构建形式仅限于简略的我的项目,如果在生产中,我的项目简单,多个入口,咱们就不可能每次打包都输出一连串的入口文件地址,也难以记住;因而个别我的项目中都应用配置文件来进行打包;配置文件的命令形式如下:
webpack [--config webpack.config.js]
配置文件默认的名称就是 webpack.config.js
,一个我的项目中常常会有多套配置文件,咱们能够针对不同环境配置不同的额文件,通过--config
来进行更换:
// 开发环境
webpack --config webpack.config.dev.js
// 生产环境
webpack --config webpack.config.prod.js
多种配置类型
config
配置文件通过 module.exports
导出一个配置对象:
// webpack.config.js
const path = require('path')
const resolve = function (dir) {return path.resolve(__dirname, dir)
}
module.exports = {
entry: {app: resolve('../index.js')
},
output: {filename: '[name].[hash:8].js',
path: resolve('../dist')
},
}
除了导出为对象,还能够导出为一个函数,函数中会带入命令行中传入的环境变量等参数,这样能够更不便的对环境变量进行配置;比方咱们能够通过 ENV
来辨别不同环境:
const path = require('path')
const resolve = function (dir) {return path.resolve(__dirname, dir)
}
module.exports = function(ENV, argv) {
return {
// 其余配置
entry: resolve('../index.js'),
output: {}}
}
还能够导出为一个 Promise,用于异步加载配置,比方能够动静加载入口文件:
entry: () => './demo'
或
entry: () => new Promise((resolve) => resolve(['./demo', './demo2']))
入口
正如在下面提到的,入口是整个依赖关系的终点入口;咱们罕用的单入口配置是一个页面的入口:
module.exports = {entry: resolve('../index.js')
}
然而咱们我的项目中可能不止一个模块,因而须要将多个依赖文件一起注入,这时就须要用到数组了:
module.exports = {
entry: [
'@babel/polyfill',
resolve('../index.js')
]
}
如果咱们我的项目中有多个入口终点,则就须要用到对象模式了:
// webpack 就会构建两个不同的依赖关系
module.exports = {
entry: {app: resolve('../index.js'),
share: resolve('../share.js')
}
}
输入
output
选项用来管制 webpack 如何输出编译后的文件模块;尽管能够有 多个 entry
,然而只能配置 一个 output
:
module.exports = {entry: resolve('../index.js'),
output: {
filename: 'index.js',
path: resolve('../dist')
},
}
这里咱们配置了一个单入口,输入也就是 index.js
;然而如果存在多入口的模式就行不通了,webpack 会提醒Conflict: Multiple chunks emit assets to the same filename
,即多个文件资源有雷同的文件名称;webpack 提供了 占位符
来确保每一个输入的文件都有惟一的名称:
module.exports = {
entry: {app: resolve('../index.js'),
share: resolve('../index.js'),
},
output: {filename: '[name].bundle.js',
path: resolve('../dist')
},
}
这样 webpack 打包进去的文件就会依照入口文件的名称来进行别离打包生成三个不同的 bundle 文件;还有以下不同的占位符字符串:
占位符 | 形容 |
---|---|
[hash] | 模块标识符 (module identifier) 的 hash |
[chunkhash] | chunk 内容的 hash |
[name] | 模块名称 |
[id] | 模块标识符 |
[query] | 模块的 query,例如,文件名 ? 前面的字符串 |
在这里引入 module
、chunk
和bundle
的概念,下面代码中也常常会看到有这两个名词的呈现,那么他们三者到底有什么区别呢?首先咱们发现 module
是经常出现在咱们的代码中,比方 module.exports
;而chunk
常常和 entry
一起呈现,bundle
总是和 output
一起呈现。
- module:咱们写的源码,无论是 commonjs 还是 amdjs,都能够了解为一个个的 module
- chunk:当咱们写的 module 源文件传到 webpack 进行打包时,webpack 会依据文件援用关系生成 chunk 文件,webpack 会对这些 chunk 文件进行一些操作
- bundle:webpack 解决好 chunk 文件后,最初会输入 bundle 文件,这个 bundle 文件蕴含了通过加载和编译的最终源文件,所以它能够间接在浏览器中运行。
咱们通过上面这张图看能够加深对这三个概念的了解:
hash、chunkhash、contenthash
了解了 chunk 的概念,置信下面表中 chunkhash 和 hash 的区别也很容易了解了;
- hash:是跟整个我的项目的构建相干,只有我的项目里有文件更改,整个我的项目构建的 hash 值都会更改,并且全副文件都共用雷同的 hash 值。
- chunkhash:跟入口文件的构建无关,依据入口文件构建对应的 chunk,生成每个 chunk 对应的 hash;入口文件更改,对应 chunk 的 hash 值会更改。
- contenthash:跟文件内容自身相干,依据文件内容创立出惟一 hash,也就是说文件内容更改,hash 就更改。
模式
在 webpack2 和 webpack3 中咱们须要手动退出插件来进行代码的压缩、环境变量的定义,还须要留神环境的判断,非常的繁琐;在 webpack4 中间接提供了模式这一配置,开箱即可用;如果疏忽配置,webpack 还会收回正告。
module.exports = {mode: 'development/production'}
开发模式是通知 webpack,我当初是开发状态,也就是打包进去的内容要对开发敌对,便于代码调试以及实现浏览器实时更新。
生产模式不必对开发敌对,只须要关注打包的性能和生成更小体积的 bundle。看到这里用到了很多 Plugin,不必慌,上面咱们会一一解释他们的作用。
置信很多童鞋都曾有过疑难,为什么这边 DefinePlugin
定义环境变量的时候要用 JSON.stringify("production")
,间接用"production"
不是更简略吗?
咱们首先来看下 JSON.stringify("production")
生成了什么;运行后果是 ”"production"
“,留神这里,并不是你眼睛花了或者屏幕上有小黑点,后果的确比 "production"
多嵌套了一层引号。
咱们能够简略的把 DefinePlugin
这个插件了解为将代码里的所有 process.env.NODE_ENV
替换为字符串中的内容。如果咱们在代码中有如下判断环境的代码:
// webpack.config.js
module.exports = {
plugins: [
new webpack.DefinePlugin({"process.env.NODE_ENV": "production"}),
]
}
// index.js
if (process.env.NODE_ENV === 'production') {console.log('production');
}
这样生成进去的代码就会编译成这样:
//dist/bundle.js
// 代码中并没有定义 production 变量
if (production === 'production') {console.log('production');
}
然而咱们代码中可能并没有定义 production
变量,因而会导致代码间接报错,所以咱们须要通过 JSON.stringify
来包裹一层:
//webpack.config.js
module.exports = {
plugins: [
new webpack.DefinePlugin({//"process.env.NODE_ENV": JSON.stringify("production")
// 相当于
"process.env.NODE_ENV": '"production"'
}),
]
}
//dist/bundle.js
if ("production" === 'production') {console.log('production');
}
生成 HTML 文件(html-webpack-plugin)
在下面的代码中咱们发现都是手动来生成 index.html,而后引入打包后的 bundle 文件,然而这样太过繁琐,而且如果生成的 bundle 文件引入了 hash 值,每次生成的文件名称不一样,因而咱们须要一个主动生成 html 的插件;首先咱们须要装置这个插件:yarn add html-webpack-plugin -D 或者 npm install html-webpack-plugin -D
应用:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 其余代码
plugins: [
new HtmlWebpackPlugin({
// 模板文件
template: resolve('../public/index.html'),
// 生成的 html 名称
filename: 'index.html',
// icon
favicon: resolve('../public/logo.ico')
}),
]
}
webpack loader
loader 用于对模块的源代码进行转换。默认 webpack 只能辨认 commonjs 代码,然而咱们在代码中会引入比方 vue、ts、less 等文件,webpack 就解决不过去了;loader 拓展了 webpack 解决多种文件类型的能力,将这些文件转换成浏览器可能渲染的 js、css。
module.rules
容许咱们配置多个 loader,可能很清晰的看出以后文件类型利用了哪些 loader。
module.exports = {
module: {
rules: [{ test: /\.css$/, use: 'css-loader'},
{test: /\.ts$/, use: 'ts-loader'}
]
}
};
loader 个性
- loader 反对链式传递。可能对资源应用流水线(pipeline)。一组链式的 loader 将依照相同的程序执行。loader 链中的第一个 loader 返回值给下一个 loader。在最初一个 loader,返回 webpack 所预期的 JavaScript。
- loader 能够是同步的,也能够是异步的。
- loader 运行在 Node.js 中,并且可能执行任何可能的操作。
- loader 接管查问参数。用于对 loader 传递配置。
- loader 也可能应用 options 对象进行配置。
- 除了应用 package.json 常见的 main 属性,还能够将一般的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
- 插件 (plugin) 能够为 loader 带来更多个性。
- loader 可能产生额定的任意文件。
loader 通过(loader)预处理函数,为 JavaScript 生态系统提供了更多能力。用户当初能够更加灵便地引入细粒度逻辑,例如压缩、打包、语言翻译和其余更多。
babel-loader
兼容低版本浏览器的痛置信很多童鞋都经验过,写完代码发现自己的 js 代码不能运行在 IE10 或者 IE11 上,而后尝试着引入各种 polyfill;babel 的呈现给咱们提供了便当,将高版本的 ES6 甚至 ES7 转为 ES5;咱们首先装置 babel 所须要的依赖:yarn add -D babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime
因为 babel-loader 的转译速度很慢,在前面咱们退出了工夫插件后能够看到每个 loader 的耗时,babel-loader 是最耗时间;因而咱们要尽可能少的应用 babel 来转译文件,正则上应用 $ 来进行准确匹配,通过 exclude 将 node_modules
中的文件进行排除,include
将只匹配 src
中的文件;能够看进去 include 的范畴比 exclude 更放大更准确,因而也是举荐应用 include。
// 省略其余代码
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
include: [resolve('src')]
use: {
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', { targets: "defaults"}]
],
plugins: ['@babel/plugin-proposal-class-properties']
}
}
}
]
}
file-loader 和 url-loader
file-loader
和 url-loader
都是用来解决图片、字体图标等文件;url-loader
工作时候两种状况:当文件大小小于 limit 参数,url-loader
将文件转为 base-64 编码,用于缩小 http 申请;当文件大小大于 limit 参数时,调用 file-loader
进行解决;因而咱们优先应用url-loader
。
module: {
rules: [
{test: /\.(jpe?g|png|gif)$/i, // 图片文件
use: [
{
loader: 'url-loader',
options: {
// 10K
limit: 1024,
// 资源门路
outputPath: resolve('../dist/images')
},
}
],
exclude: /node_modules/
},
]
}
搭建 webpack 开发环境
在下面咱们都是通过命令行打包生成 dist 文件,而后间接关上 html 或者通过 static-server 来查看页面的;然而开发中咱们写完代码每次都来打包会重大影响开发的效率,咱们冀望的是写完代码后立刻就可能看到页面的成果;webpack-dev-server
就很好的提供了一个简略的 web 服务器,可能实时从新加载。
webpack-dev-server
的用法和 wepack
一样,只不过他会额定启动一个 express
的服务器。咱们在我的项目中 webpack.config.dev.js
配置文件对开发环境进行一个配置:
module.exports = {
mode: 'development',
plugins: [new Webpack.HotModuleReplacementPlugin()
],
devtool: 'cheap-module-eval-source-map',
devServer: {
// 端口
port: 3300,
// 启用模块热替换
hot: true,
// 主动关上浏览器
open: true,
// 设置代理
proxy:{
"/api/**":{
"target":"http://127.0.0.1:8075/",
"changeOrigin": true
}
}
}
}
通过命令行 webpack-dev-server
来启动服务器,启动后咱们发现根目录并没有生成任何文件,因为 webpack
打包到了内存中,不生成文件的起因在于拜访内存中的代码比拜访文件中的代码更快。
咱们在 public/index.html
的页面上有时候会援用一些本地的动态文件,间接关上页面的会发现这些动态文件的援用生效了,咱们能够批改 server
的工作目录,同时指定多个动态资源的目录:
contentBase: [path.join(__dirname, "public"),
path.join(__dirname, "assets")
]
热更新(Hot Module Replacemen 简称 HMR)是在对代码进行批改并保留之后,webpack 对代码从新打包,并且将新的模块发送到浏览器端,浏览器通过新的模块替换老的模块,这样就能在不刷新浏览器的前提下实现页面的更新。
webpack plugins
下面介绍了 DefinePlugin、HtmlWebpackPlugin 等很多插件,咱们发现这些插件都可能不同水平的影响着 webpack 的构建过程,上面还有一些罕用的插件:
clean-webpack-pluginclean-webpack-plugin
用于在打包前清理上一次我的项目生成的 bundle 文件,它会依据 output.path 主动清理文件夹;这个插件在生产环境用的频率十分高,因为生产环境常常会通过 hash 生成很多 bundle 文件,如果不进行清理的话每次都会生成新的,导致文件夹十分宏大。
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
plugins: [new CleanWebpackPlugin(),
],
}
mini-css-extract-plugin
咱们在应用 webpack 构建工具的时候,通过style-loader
,能够把解析进去的 css 通过 js 插入外部样式表的形式到页面中,mini-css-extract-plugin 插件也是用来提取 css 到独自的文件的,该插件有个前提条件,只能用于 webpack 4 及以上的版本,所以如果应用的 webpack 版本低于 4,, 那还是用回 extract-text-webpack-plugin 插件。
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
// 省略其余代码
module: {
rules: [
{
test: /\.less$/,
use: [
{loader: dev ? 'style-loader': MiniCssExtractPlugin.loader},
{loader: 'css-loader'},
{loader: 'less-loader'}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({filename: "[name].[hash:8].css",
})
]
}
copy-webpack-plugin
咱们在 public/index.html 中引入了动态资源,然而打包的时候 webpack 并不会帮咱们拷贝到 dist 目录,因而 copy-webpack-plugin 就能够很好地帮我做拷贝的工作了。
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [new CleanWebpackPlugin(),
new CopyWebpackPlugin([{from: path.resolve(__dirname, '../static'),
to: path.resolve(__dirname, '../dist/static')
}])
],
}
ProvidePlugin
ProvidePlugin
能够很快的帮咱们加载想要引入的模块,而不必 require
。个别咱们加载jQuery
须要先把它import
:
import $ from 'jquery'
$('#layout').html('test')
然而咱们在 config 中配置 ProvidePlugin
插件后可能不必 import,间接应用$
:
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
}),
]
}
在我的项目中引入了太多模块并且没有 require
会让人摸不着头脑,因而倡议加载一些常见的比方 jQuery、vue、lodash 等。
loader 和 plugin 的区别(面试中常遇见)
- 对于 loader,它是一个转换器,将 A 文件进行编译造成 B 文件,这里操作的是文件,比方将 A.scss 转换为 A.css,单纯的文件转换过程
- plugin 是一个扩展器,它丰盛了 webpack 自身,针对是 loader 完结后,webpack 打包的整个过程,它并不间接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行宽泛的工作