webpack知识点
安装
// 每次更新都要npm run build
1. npm install webpack webpack-cli --save-dev
// 只有本地开发会用到
2. npm install webpack-dev-server --save-dev
package.json:
'scripts': {
'build': 'webpack',
'dev': 'webpack-dev-server
}
在webpack.config.js中配置devServer
wepack-dev-server主要职责:
- 令webpack进行模块打包,并处理打包结果的资源请求
- 作为普通的we server,处理静态资源文件请求
wepack-dev-server只是将结果写在内存里,每次接受到请求从内存读取,并不会生成bundle.js
// 专门放webpack-dev-server的配置
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js'
},
mode: 'development',
devServer: {
publicPath: '/dist'
}
}
// 执行npm run dev
配置入口
- context 在多入口的情况下,使entry更简洁,可以省略
module.exports = {
context: path.join(__dirname, './src/scripts'), // 默认工程根目录
entry: './index.js
}
- entry 可以字符串,数组,对象,函数
1.字符串
module.exports = {
entry: './src/index.js,
output: {
filename: 'bundle.js
},
mode: 'development'
}
2.数组
entry: ['babel-polyfill': './src/index.js'],
相当于
// webpack.config.js:
entry: './src/index.js'
// index.js
import 'babel-polyfill'
3.对象:如果使用多入口,必须使用对象
entry: {
index: './src/index.js', // chunk name为index 入口路径就是./src/index.js
lib: './src/lib.js'
}
对象的属性值也可以为字符串或数组
entry: {
index: ['babel-polyfill': './src/index.js'],
lib: './src/lib.js'
}
- 在使用字符串或数组定义单入口时,没法改变chunk name,只能时默认的main
使用多入口时,必须为每个入口定义chunk name
4.函数: 只要返回上面的任何配置形式就行
entry: () => './src/index.js'
传入函数的优点:可以在函数体内添加动态的逻辑来获取工程的入口
函数也支持返回一个Promise对象来进行异步操作
// 模拟异步
entry: () => new Promise(resolve => {
setTimeout(() => {
resolve('./src/index.js')
}, 1000)
})
vendor 供应商,一般指工程中所使用的库,框架等第三方模块集中打包产生的bundle
module.exports = {
context: path.join(__dirname, './src'),
entry: {
app: './src/app.js',
// 添加新的chunk name为 vendor的入口,通过数组形式把工程依赖的第三方模块放进去
vendor: ['react', 'react-dom', 'react-router']
}
}
使用CommonsChunkPlugin(webpack4之后已被废弃,可用optimization.splitChunks)
将app 和 vendor 这两个 chunk 中的公共模块提取出来
第三方模块会被抽成新的bundle,利用客户端缓存,在后续请求页面时,加快渲染速度
多页应用
每个页面都有一个独立的bundle
entry: {
pageA: './src/pageA.js',
pageB: './src/pageB.js',
pageC: './src/pageC.js',
}
入口和页面是 1对1的关系,这样每个HTML只要引入各自的js就可以家在所需要的模块
也可提取vendor的方法,将公共模块打包
entry: {
pageA: './src/pageA.js',
pageB: './src/pageB.js',
pageC: './src/pageC.js',
vendor: ['react', 'react-dom']
}
配置出口
- filename
控制输出文件的名称,也可以是一个相对路径,如果没有该路径会创建 ./js/bundle.js
const path = require('path')
module.exports = {
entry: './src/app.js',
output: {
filename: 'bundle.js',
path.join(__dirname, 'assets'),
pablicPath: '/dist/'
}
}
多入口时,可使用类似模版语言动态生成文件名
entry: {
app: './src/app.js',
vendor: './src/vendor.js'
},
output: {
filename: '[name].js'
}
在资源输出时,[name]会被替换成chunk name, 因此实际项目生成 vendor.js和 app.js
其它模版变量:
[hash] webpack此次打包所有资源生成的hash
[chunkhash] 当前chunk内容的hash
[id] 当前chunk的id
[query] filename 配置项中的query
以上变量作用:
- 当有多个chunk时,对不同chunk区分,如[name],[chunkhash],[id]对每个chunk都是不同的
- 控制客户端缓存,[hash],[chunkhash]与chunk内容直接相关,当chunk内容更改时,可以同时引起
资源文件名的更改,下次请求文件会下载最新版本而不使用本地缓存
[query]也起到类似效果,只不是它与chunk内容无关,需要由开发者手动绑定
- 在实际中,使用多的是[name],它与chunk是一对一的关系,且可读性高,如果要控制客户端缓存最好
加上[chunkhash],因为每个chunk所产生的[chunkhash]只与自身内容有关,单个chunk内容的改变
不会影响其它资源,可以最精确的让客户端缓存得到更新
module.exports = {
entry: {
app: './src/app/js',
venfor: './src/vendor/js'
},
output: {
filename: '[name]@[chunkhash].js'
}
}
打包结果:
vendor@0ddfasddfrfgrtg.js
app@fksejrgorjgojh.js
更新混存一般只用在生产环境的配置下,在开发环境中可以不必配置chunkhash
- path 指定资源输出的位置,要求值必须为绝对路径
const path = require('path')
module.exports = {
entry: './src/app.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
}
}
webpack4之后,默认在dist目录,除非要更改,否则不必单独配置
- publicPath
- path 用来指定资源的输出位置
输出位置:打包完成后资源产生的目录,一般指定为工程中的dist目录 - publicPath 指定资源的请求位置
由js 或 css 所请求的简介资源路径,页面中的资源分为两种:- 由html页面直接请求,如通过script标签家在的js
- 由js 或 css 请求的,如异步加载的js, 从 css 请求的图片字体等
publicPath作用就是指定这部分间接资源的请求位置
- 与html相关
可将publicPath指定为html的相对路径
在请求时,会以当前页面html所在路径加上相对路径,构成实际请求的url
// html: https://example.com/app/index.html
// 异步加载的资源: 0.chunk.js
publicPath: '' // 实际路径为 https://example.com/app/0.chunk.js
publicPath: './js' // 实际路径为 https://example.com/app/js/0.chunk.js
publicPath: '../assets' // 实际路径为 https://example.com/assets/0.chunk.js
- host相关
publicPath值以/开始,则代表此时的publicPath是以当前页面的host name为基础路径的
// 假设当前html地址为 https://example.com/app/index.html
// 异步加载资源: 0.chunk.js
publicPath: '/' // 实际路径:https://example.com/0.chunk.js
publicPath: '/js/' // 实际路径:https://example.com/js/0.chunk.js
publicPath: '/dist/' // 实际路径:https://example.com/dist/0.chunk.js
- cdn相关
当publicPath以协议头或相对协议的形式开始,代表当前路径是cdn相关
// 假设当前html地址为 https://example.com/app/index.html
// 异步加载资源: 0.chunk.js
publicPath: 'http://cdn.com' // 实际路径:http://cdn.com/0.chunk.js
publicPath: 'https://cdn.com' // 实际路径:https://cdn.com/0.chunk.js
publicPath: '//cdn.com/assets/' // 实际路径://cdn.com/assets/0.chunk.js
webpack-dev-server中也有个publicPath,与上面的那个不一样,指的是
webpack-dev-server静态资源服务路径
最好设置成一样的,保持开发环境和生产环境一样
const path = require('path')
module.exports = {
entry: './src/app.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
devServer: {
publicPath: 'dist',
port: 3000
}
}
// localhost:3000/dist/bundle.js 可以得到预期结果
实例
- 单入口
不必设置动态的output.filename直接指定输出文件名即可
const path = require('path')
module.exports = {
entry: './src/app.js',
output: {
filename: 'bundle.js'
},
devServer: {
publicPath: '/dist' // webpack4以后output.publicPath默认为dist
}
}
- 多入口
必须使用模版变量来配置filename
module.exports = {
entry: {
pageA: './src/pageA.js',
pageB: './src/pageB.js',
},
output: {
filename: '[name].js'
},
devServer: {
publicPath: '/dist/'
}
}
// 最终生成:
pageA.js
pageB.js
生成环境可以配置成[name]@[chunkhash].js
预处理器
1.webpack 一切皆模块的思想 与 loader概念
- loader原理
- 如何引入一个loader
- 常用loader介绍
- 如何便携一个loader
一切皆模块
html js css 模版 图片 字体等多种类型的静态资源,对webpack来说都是模块
可以像加载js文件一样加载他们
如:
// index.js
import './style.css
这句引用的实际意义就是描述了js与css文件之间的依赖关系
webpack本身只认识js, 其它类型必须预先定义一个或多个loader来转译成webpack能接受的形式
loader 的引入
npm install css-loader
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
use: ['css-loader']
}
]
}
}
// 此时再打包,就可以把.css文件打包进去了,但是页面上没有生效,因为css-loader仅仅处理了各种加载
语法(@import 和 url())函数等,style-loader可以把样式插入页面,css-loader 和 style-loader
通常是配合一起使用的
test 可接受一个正则表达式或一个元素为正则表达式的数组,只有正则匹配上的模块才会使用这条规则
use [] 包含该规则使用的loader
链式loader
npm install style-loader
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
}
}
webpack打包时是按照从后往前的顺序将资源给loader处理的,因此要把最后生效的放在最前面
loader options
rules: [{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
// css-loader 配置项
}
}
]
}]
有些loader会用query来代替options,功能上一样
更多配置
- exclude 与 include
排除或包含指定目录下的模块,可接收正则表达式或字符串(文件绝对路径),以及由他们组成的数组
rules: [{
test: /\.css$/,
use: ['style-loader','css-loader'],
// 所有被正则匹配到的模块都排除在该规则之外,node_modules下的模块不会执行这条规则
// 此配置项通常是必加的,否则会拖慢整体打包速度
exclude: /node_modules/,
}]
通常用babel-loader来处理es6+,但是对于node_modules中的js文件来说,很多以及编译成es5了,没必要额外处理
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
// 该规则只对正则匹配到的模块生效
include: /src/,
}
]
如果include 和 exclude 同时存在,exclude优先级更高
test: /\.css$/,
use: ['style-loader','css-loader'],
exclude: /node_modules/,
// 这个不起作用的哦
// include: /node_modules\/awsome-ui/
可以改成
// 排除node_modules中除了foo和bar之外的所有模块
exlude: /node_modules\/(?!(foo|bar)\/).*/
由于exclude优先级更高,可对include中子目录进行排除
exclude: /src/lib/,
include: /src/
resource 和 issuer
用于更加精确的确定模块规则的作用范围
在Webpack中,认为
resource 是 被加载模块
issuer 是 加载者
如:index.js:
import './style'
resource是 /path/of/app/style.css
iusser是 /path/of/app/index.js
前面的test exclude include 是对resource被加载者的配置
如果要对issuer加载者也增加条件限制,如让/src/pages下的js
可以引用css
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
exclude: /node_modules/,
issuer: { // 只有src/pages/下面的js文件引用css文件,这条规则才生效
test: /\.js$/,
include: /src/pages/,
}
}
]
上面的可读性差 可 改成下面这种
rules: [
{
use: ['style-loader', 'css-loader'],
resource: {
test: /\.css$/,
exclude: /node_modules/,
}
issuer: {
test: /\.js$/,
include: /src/pages/,
}
}
]
enforce 用来指定一个loader的种类,只接收 'pre' 'post' 两种字符串类型的值
wepack中loader的执行顺序可分为pre inline normal post四种类型
上面直接定义的loader都属于normal类型,inline形式官方已经不在推荐
而 pre 和 post 需要enforce指定
rules: [
{
test: /\.js$/,
enforce: 'pre', // 代表在所有正常loader执行之前,保证代码不是被其它loader更改过的
use: 'eslint-loader'
}
]
在所有loader之后执行,enforce设置为post
事实上,可以不使用enforce,只要保证loader顺序是正确的即可,配置ecforce的目的:使模块规则更加清晰,可读性更强
在实际项目中,配置文件可能达到上百行,难保哥哥loader都按照预想的方式执行, enforce可以强制指定loader的执行顺序
常用loader
- babel-loader es6+ 编译为es5 使我们可以使用语言最新特性(甚至在提案中的),不必考虑不同平台的兼容问题
npm install babel-loader @babel/core @babel/preset-env
- babel-loader 使babel webpack协同国内工作的模块
- @babel/core babel编译器的核心模块
- @babel/preset-env 官方推荐的预置器,可根据用户设置的目标环境自动添加所需的插件和布丁来编译es6代码
rules: [
{
test: /\.js$/,
exclude: /node_module/, // 对所有js文件设置的规则,所以排除node_modules
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true, // 启动缓存机制,重复打包未改变过的模块时,防止二次编译
presets: [[
'env',
{
// @babel/preset-env 会将es6 module转为common.js 会导致webpack的tree-shaking失效
modules: false, // 禁用模块语句的转化,将es6 module的语法交给webpack处理
}
]]
}
}
}
]
cacheDirectory 也可以接受一个字符串的路径来作为缓存路径, 设置为true时,缓存目录为:node_modules/.cache/babel-loader
- ts-loader 用于链接webpack与ts模块
npm install ts-loader typescript
rules: [
{
test: /\.ts$/,
use: 'ts-loader'
}
]
ts本身的配置不在ts-loader中,在tsconfig.json中
如:
"compilerOptions": {
"target": "es5",
"sourceMap": true
}
- html-loader
用于将HTML文件转化为字符串并进行格式化,这样可以把HTML片段通过js加载进来
npm install html-loader
rules: [
{
test: /\.html$/,
use: 'html-loader'
}
]
使用事例:
header.html
<header>
<h1>this is a header</h1>
</header>
index.js
import headerHtml from './header.html'
document.write(headerHtml)
header.html会转化为字符串,并通过document.write插入页面
- handlebars-loader
用于处理handlebars模版,在安装时,要额外安装hanlebars
npm install handlebars-loader handlebars
rules: [
{
test: /\.handlebars$/,
use: 'handlebars-loader'
}
]
- file-loader
打包文件类型的资源,并返回publicPath
npm install file-loader
const path = require('path')
module.exports = {
entry: './app.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.png|jpg|gif&/,
use: 'file-loader'
}
]
}
}
可以在js中加载图片了
import avatarImage from './avatar.png'
console.log(avatarImage) // gserygretyrsgert.png
output.path是资源打包输出路径
output.publicPath 是资源引用路径
打包完后生成 gserygretyrsgert.png 的图片文件。由于默认配置中未指定output.publicPath
这里打印的只是文件名,默认为文件的hash值加上文件后缀
下面加上output.publicPath
const path = require('path')
module.exports = {
entry: './app.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: './assets'
},
module: {
rules: [
{
test: /\.(png|jpg|gif)$/
use: 'file-loader'
}
]
}
}
此时的路径: ./assets/gserygretyrsgert.png
file-loader可配置文件名以及publicPath 会覆盖output.publicPath
rules: [
{
test: /\.(png|jpg|gif)$/
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
publicPath: './another-path'
}
}
}
]
此时的路径:./another-path/gserygretyrsgert.png
- url-loader
与file-loader类似,唯一的不用是可以可以设置文件大小的阀值,当大于该阀值时与file-loader一样返回publicPath,
小于该阀值时:返回文件base64形式编码
npm install url-loader
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 10240,
name: '[name].[ext]',
publicPath: './assets-path'
}
}
}
]
import avatarImage from './avatar.jpg'
console.log(avatarImage)
// data:image/jpeg;base64,/9j/....
- vue-loader
处理vue组件,可将组件模版,js 样式进行拆分,安装时还要安装vue-template-compiler来编译vue模版
以及css-loader来处理样式(如果是scss,less还需要对应的loader)
npm install vue-loader vue vue-tamplate-complier css-loader
rules: [
{
test: /\.vue$/,
use: 'vue-loader' // 支持更多高级配置哦~
}
]
自定义loader
- loader初始化
npm init -y
touch index.js
index.js:
module.exports = function(content) {
var userStrictPrefix = '\'use strict\';\n\n;
return userStrictPrefix + content
}
npm install <path-to-loader>/force-strict-loader
// 在webpack工程目录下使用相对路径安装,会在项目的mode_modules中创建一个指向实际force-strict-loader目录的软链,
// 也就是说可以随时修改loader源码,不需要重新安装
webpack.config.js:
module: {
rules: [
{
test: /\.js$/,
use: 'force-strict-laoder
}
]
}
- this.cacheable启用缓存
当文件输入和其依赖没有变化时,让loader直接使用缓存
// force-strict-loader/index.js
module.exports = function(content) {
if (this.cacheable) {
this.cacheable()
}
var userStrictPrefix = '\'use strict\';\n\n;
return userStrictPrefix + content
}
- 获取options
loader的配置项通过options传进来
module: {
rules: [
{
test: /\.js$/,
use: 'force-strict-laoder,
options: {
sourceMap: true
}
}
]
}
要在自己的loader中获取这个配置就要安装loader-utils, 主要提供一些帮助函数,在force-strict-loader目录下执行一下命令
npm install loader-utils
修改loader
var loaderUtils = require('loader-utils')
module.exports = function(content) {
if (this.cacheable) {
this.cacheable()
}
// 获取options配置对象
var options = loaderUtils.getOptions(this) || {}
console.log(options)
var userStrictPrefix = '\'use strict\';\n\n;
return userStrictPrefix + content
}
- source-map
便于开发者在浏览器控制台查看源码,如果没有source-map处理,最终无法生成正确的map文件 devtool中看到的就是乱的源码
修改loader
var loaderUtils = require('loader-utils')
var SourceNode = require('source-map').SourceNode
var SourceMapConsumer = require('source-map').SourceMapConsumer
module.exports = function(content, sourceMap) {
var userStrictPrefix = '\'use strict\';\n\n;
if (this.cacheable) {
this.cacheable()
}
// 获取options配置对象
var options = loaderUtils.getOptions(this) || {}
console.log(options)
// source-map处理
if(options.sourceMap && sourceMap) {
var currentRequest = loaderUtils.getCurrentRequest(this)
var node = SourceNode.fromStringWithSourceMap(
content,
new SourceMapConsumer(sourceMap)
)
node.prepend(userStrictPrefix)
var result = node.toStringWithSourceMap({
file: currentRequest
})
var callback = this.async()
callback(null, result.code, result.map.toJSON())
}
// 不支持source-map情况
return userStrictPrefix + content
}