本篇文章解答了60多道关于webpack
的问题,答案有详细有简单,时间关系,有的问题还未做出解答,后续会补上,持续更新。如果答案有不对的地方,请轻拍。
1、全局安装和本地安装,开发依赖和项目依赖有什么区别
1-1、全局安装
全局安装webpack
,意味着webpack
包被安装到npm的全局包目录里了。
查看npm
全局包位置:
npm root -g
附加点npm
的知识:
查看npm
配置信息:
npm config ls
查看npm
下载源:
npm config get registry
查看npm
安装目录:
npm config get prefix
这里假设通过命令npm config get prefix
获取到的路径为C:\Users\wz\AppData\Roaming\npm
,为了方便起见,用A
代替C:\Users\wz\AppData\Roaming
通过这个目录可以获取到以下信息:
- 1、
npm
全局配置文件(A\npm\etc
) - 2、全局安装的包(
A\npm\node_modules
) - 3、全局命令(npx link生成的命令)(
A\npm
) - 4、npm缓存(
A\npm-cache
)
1-2、本地安装
本地安装webpack
,webpack
包被安装到了项目根目录下的node_modules
里了。
1-3、开发依赖
开发依赖,指的是只有在项目开发、编译、打包、压缩等过程才会用到的包,一旦文件成功产出,就不再需要他们了,比如:less-loader
、webpack
等等。
1-4、项目依赖
项目依赖,指的是从开始编写项目到项目上线以后都需要的包,比如:vue
、axios
等等。
2、使用webpack需要安装哪些模块
需要安装:
webpack
(webpack
核心包)webpack-cli
(webpack
命令行工具,依赖于webpack
核心包)
3、webpack默认配置文件(2种)的名字是什么
webpack
默认配置文件:
第一种:webpack.config.js
第二种:webpackfile.js
webpack
源码中和配置相关的代码,位置:webpack/bin/convert-argv.js
//...
//从这里可以看出webpack的2中配置文件
var defaultConfigFiles = ["webpack.config", "webpackfile"].map(function(filename) {
return extensions.map(function(ext) {
return {
path: path.resolve(filename + ext),
ext: ext
};
});
}).reduce(function(a, i) {
return a.concat(i);
}, []);
//...
4、webpack打包后的结果为什么可以在浏览器中执行
为什么会在浏览器里执行,就要从webpack
打包后的文件中去找答案了。
搭建一个简单的项目,只安装webpack
这个包,项目目录如下:
index.js
如下:
function foo() {
console.log(window.localStorage)
}
module.exports = { foo }
webpack.config.js
如下:
module.exports = {
mode:'development',
entry: './src/index.js',
output: {
path: require('path').resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
}
在命令行执行webpack
命令后,打包后的文件内容,经过删减,总体"框架"代码如下:
(function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
return __webpack_require__("./src/index.js");
})({
"./src/index.js": (function (module, exports) {
eval(/* ... */)
})
})
分析以上代码:
1、总体是一个自执行函数,双括号形式。
(function(modules){/* ... */})({})
一个自执行函数当然可以在浏览器中执行了。
2、自执行函数的函数体内,定义了模块缓存对象installedModules
,定义了模块加载方法__webpack_require__
,这个__webpack_require__
方法就是为浏览器量身打造的,作用相当于node
中的require
方法。__webpack_require__
方法的逻辑也不难理解,首先通过模块名(其实就是一个路径)去模块缓存对象上查找,找不到的话,就新建一个模块module
并缓存到installedModules
上,然后通过模块名找到对应的模块函数,执行它,并将module
等参数传入,最后返回模块导出对象module.exports
。这段代码建议仔细看看
3、自执行函数的参数。该参数是一个对象,类似下面这样:
{
"./src/index.js": (function (module, exports) {eval(/* ... */)})
}
该对象的键是一个路径字符串,其实就是我们调用require方法时传入的模块路径; 值为一个接收
module
和exports
参数的函数,函数体内是一个包裹着一堆字符串代码的eval
函数,这一堆字符串代码就是我们写的代码。可见,webpack
为了让我们的代码能够在浏览器里执行,做了多少工作。
5、webpack
有几种模式,对应模式的作用
webpack
有3种模式:
development
production
none
简单来说,不同的模式下,webpack会启用不同的插件,或者不同的优化手段。
development
模式下,会启用以下插件:
- new webpack.NamedModulesPlugin()
- new webpack.NamedChunksPlugin()
production
模式下,会启用以下插件:
- new UglifyJsPlugin(/* ... */)
- new webpack.optimize.ModuleConcatenationPlugin()
- new webpack.NoEmitOnErrorsPlugin()
none
模式下,不会启动任何插件。
详情可参考这篇文章
6、配置执行命令的脚本在package.json
中如何配置
在package.json
文件中的script
字段里配置,如下,我们配置3条命令,分别为dev
、pro
、start
:
{
"name": "l",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev":"", //dev命令
"pro":"", //pro命令
"start":"" //start命令
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.35.3"
}
}
配置完成后,就可以在项目的命令行里执行以下命令了:
npm run dev
npm run pro
npm run start
7、如何指定webpack-dev-server
的启动目录
修改和webpack-dev-server
有关配置的contentBase
选项。
let path = require('path')
module.exports = {
mode:'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename:'bundle.js'
},
devServer: {
port: 8888,//监听的端口号
progress: true,//启动打包进度显示
contentBase: path.join(__dirname, 'dist'),//这里指定启动目录
compress: true //启动压缩
}
}
关于webpack-dev-server
的更多配置,点击这里
从打包的过程也可以看出webpack-dev-server
的启动目录,如下:
8、如何压缩产生的html文件
使用html-webpack-plugin
插件,配置如下:
plugins: [
new HtmlWebpackPlugin({
template: './index1.html',
filename: 'main.html',
minify: {
collapseWhitespace: true,//移除空格
removeAttributeQuotes:true//移除属性的双引号
}
})
]
官方说如果模式为production
的话,minify
选项会被默认设置成true
,产出的HTML
文件会自动压缩,大家可以试试,我尝试的不行。
minify
的配置选项有很多,感兴趣可以点这里查看更多。
其实,html-webpack-plugin
可以压缩HTML
文件,内部是依赖的是这个库html-minifier
,这个压缩HTML
的库也可以这样使用:
var minify = require('html-minifier').minify;
var result = minify('<p title="blah" id="moo">foo</p>', {
removeAttributeQuotes: true
});
result; // '<p title=blah id=moo>foo</p>'
关于html-minifier
的更多信息,这里
9、如何解决打包后缓存的问题
JavaScript
文件解决方法:
第一种:
让文件名称带有hash
字符串,这样每次打包js
文件时,只有内容有变化,hash
字符串就会发生变化,比如下面:
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:8].bundle.js'
},
这里打包后的文件名称为main.d3a5dd20.bundle.js
。
第二种:
html-webpack-plugin
的配置项hash
置为true
,这样打包后的js
文件在插入html
文件后会以?
开始添加hash
字符串。如下:
plugins: [
new HtmlWebpackPlugin({
template: './index1.html',
filename: 'main.html',
hash:true
})
]
这里打包后的文件名称如下:
<body>
<h1>This is a title</h1>
<script type="text/javascript" src="main.js?d3a5dd204b4d1b64170c"></script>
</body>
10、webpack中的loader是干什么的
loader
的作用是对源代码进行转换,webpack
只认识js
、json
2种文件,其他类型的文件,比如css
、img
、less
等等,只能依靠loader
去解析转换了。
官方解释,这里
11、css-loader和style-loader的作用
css-loader
主要处理css
文件中@import
和url()
语法的。官方文档
style-loader
主要作用是将css
样式以style
标签的形式插入到页面中。官方文档
12、请说出loader的特点
- 第一个
loader
要返回js
脚本 - 每个
loader
只做一件事情,为了使loader
在更多场景下链式调用 - 每一个
loader
都是一个node
模块 loader
有同步的,也有异步的loader
有两个执行阶段,pitch
、normal
官网文档
loader
的执行顺序比较讲究,
如下图所示:
13、loader有几种写法
配置文件写法、行内loader
写法、命令行写法3种
配置文件写法:
就是将配置信息写到webpack.config.js
,写法又有以下几种:
13-1、直接写loader
module.exports={
module:{
rules:[
{
test: /.js$/,
loader: 'my-loader',
exclude: /node_modules/
},
]
}
}
13-2、使用use
,字符串形式
module.exports={
module:{
rules[
{
test: /.js$/,
use: 'my-loader',//直接传递字符串
exclude: /node_modules/
},
]
}
}
13-3、使用use
,对象形式
module.exports={
module:{
rules[
{
test: /.js$/,
use: { //对象形式,可以给loader传递参数
loader:'my-loader',
options:{}//这里传递参数给loader
}
exclude: /node_modules/
},
]
}
}
13-4、使用use
,数组形式
数组内的每一项可以为字符串,也可以是对象。
module.exports = {
module: {
rules: [
{
test: /.js$/,
use: [
'my-loader1',//字符串形式
{ loader: 'my-loader2', options: {} }//对象形式
],
exclude: /node_modules/
},
]
}
}
行内loader
写法:
多个loader
之间用!
分割。
let something=require('loader2!loader1!./profile.js')
行内loader
可添加前缀,代表当前文件是否交由其他loader
处理:
-!
表示不会让文件再去通过pre+normal
loader
处理了!
表示不会让normal
loader
处理了!!
该文件只会让行内loader
处理
let a = require('inline-loader!./a') // !分割,inline-loader就是行内loader
let a = require('-!inline-loader!./a') // -!表示不会让文件再去通过 pre+normal loader处理了
let a = require('!inline-loader!./a') // ! 表示不会让normal loader处理了
let a = require('!!inline-loader!./a') // !! 该文件只会让行内loader处理
命令行写法:
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
14、loader的默认顺序是怎样的
从右向左,从下到上
{
test: /\.js$/,
use: ['loader3', 'loader2', 'loader1']
}
以上loader
执行顺序为 loader1
---> loader2
--->loader3
{
test: /\.js$/,
use: {
loader:'loader3'
}
},
{
test: /\.js$/,
use: {
loader: 'loader2'
}
},
{
test: /\.js$/,
use: {
loader: 'loader1'
}
}
以上loader
执行顺序为 loader1
--->loader2
--->loader3
15、使用loader时如何传递参数
第一种:在配置文件中传递参数
module.exports={
module:{
rules[
{
test: /.js$/,
use: { //对象形式,可以给loader传递参数
loader:'my-loader',
options:{}//这里传递参数给loader
}
exclude: /node_modules/
},
]
}
}
第二种:行内loader
传递参数的方法
let foo = require('expose-loader?$!./a.js')
16、如何控制loader的执行顺序
loader
的默认顺序是从右向左,从下到上,不过可以通过enforce
字段来打破这种顺序。
enforce
有两个取值:pre
代表第一个执行。post
代表最后一个执行
有如下loader
配置:
{
test: /.js$/,
use: 'loader1.js',
exclude: /node_modules/,
enforce: 'pre',//代表loader1首先被执行
},
{
test: /.js$/,
use: 'loader2.js',
exclude: /node_modules/
},
{
test: /.js$/,
use: 'loader3.js',
exclude: /node_modules/
},
{
test: /.js$/,
use: 'loader4.js',
exclude: /node_modules/,
enforce: 'post'//代表loader4最后被执行
}
如果没有配置enforce
字段,执行顺序为:loader4--->loader3--->loader2--->loader1
如果配置了enforce
字段,执行顺序为:loader1--->loader3--->loader2--->loader4
注意:没有配置enforce
字段的loader
默认为normal
,按照默认顺序执行.
如果文件在require
的时候用到了行内loader
的话,执行顺序如下:
pre
--->normal
--->inline
--->post
17、如何指定loader想要处理的js文件
思考中...
18、常见的css预处理器有哪些,对应的loader有哪些
css
预处理器:less
、sass
、stylus
对应的loader
:less-loader
、sass-loader
、stylus-loader
19、如何实现css
样式抽离
安装插件:
npm install mini-css-extract-plugin -D
配置文件:
{
module: {
rules: [
{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader,//使用插件loader
},
'css-loader'
]
},
]
},
plugins: [
//添加插件实例
new MiniCssExtractPlugin({
filename: 'index.css'
})
]
}
mini-css-extract-plugin
更多用法
20、如何实现自动添加浏览器前缀
安装包:
npm install postcss-loader autoprefixer -D
项目根目录下新建文件postcss.config.js
,内容如下:
module.exports = {
plugins: [require('autoprefixer')]
}
webpack.config.js
配置:
module: {
rules: [
{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
'postcss-loader'//这里加入了新的loader
],
include: path.join(__dirname, './src'),
exclude: /node_modules/
},
]
}
21、抽离css后如何实现css压缩
安装包:
npm install optimize-css-assets-webpack-plugin -D
webpack.config.js
配置:
{
plugins: [
new OptimizeCSSAssetsPlugin()
]
}
22、压缩js需要使用什么插件
安装包:
npm install uglifyjs-webpack-plugin --save-dev
webpack.config.js
配置:
module.exports = {
optimization: {
minimizer: [ new UglifyJsPlugin() ],
},
};
或者:
{
plugins: [
new UglifyJsPlugin()
]
}
uglifyjs-webpack-plugin
官方文档
23、想把es6转化成es5需要哪些模块
npm install @babel/core @babel/preset-env babel-loader -D
24、babel-loader和@babel/core的关系
@babel/core
是核心包,提供基础的API服务。babel-loader
依赖@babel/core
关于babel的知识,推荐阅读这两篇:
我是第一篇
我是第二篇
25、@babel/plugin-proposal-class-properties和@babel/plugin-proposal-decorators作用是什么
@babel/plugin-proposal-class-properties
是用来转换class
语法的。比如:
如下语法:
class Bork {
static a = 'foo';
static b;
x = 'bar';
y;
}
会被转换为:
var Bork = function Bork() {
babelHelpers.classCallCheck(this, Bork);
this.x = 'bar';
this.y = void 0;
};
Bork.a = 'foo';
Bork.b = void 0;
官方文档
@babel/plugin-proposal-decorators
是用来转换修饰符(@
)的。
官方文档
26、@babel/plugin-transform-runtime,@babel/runtime,@babel/polyfill区别是什么
@babel/polyfill
:
babel
默认只转换 js
语法,而不转换新的 API,比如 Iterator
、Generator
、Set
、Maps
、Proxy
、Reflect
、Symbol
、Promise
等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign
)都不会转码。
举例来说,es2015
在 Array
对象上新增了 Array.from
方法。babel
就不会转码这个方法。如果想让这个方法运行,必须使用 @babel/polyfill
。(内部集成了 core-js 和 regenerator)
使用时,在所有代码运行之前增加 require('@babel/polyfill
')。
@babel/plugin-transform-runtime
依赖@babel/runtime
,也就是说:在使用@babel/plugin-transform-runtime
的时候必须把@babel/runtime
当做依赖。
推荐阅读24题的两篇文章。
27、如何对js代码进行校验
可以使用eslint
包对js
代码进行校验。
安装包:
npm i eslint eslint-loader babel-eslint -D
新建eslint
配置文件.eslint.js
:
module.exports = {
root: true,
//指定解析器选项
parserOptions: {
sourecType: 'module'
},
//指定脚本执行的环境
env: {
browser: true
},
//启用的规则及其各自的错误级别
rules: {
"semi": "error",//语句强制分号结尾
"indent": ["error",4],//缩进风格
"quotes":["error","double"]//引号类型
}
}
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'eslint-loader',
options: {
fix: true
},
},
include: [path.resolve(__dirname, 'src')],
exclude: /node_modules/,
}
]
}
eslint
官网文档
28、请说下暴露变量的三种方式
暴露变量其实就是暴露到全局,也就是挂在到window
上。
第一种:
安装包expose-loader
npm install expose-loader -D
在入口文件(webpack.config.js
中的entry
)中使用expose-loader
,这种方式属于内联loader
。
import $ from 'expose-loader?$!jquery'
console.log(window.$)
- ! --->将
loader
和包隔开符号 - ? --->给
expose-loader
传递参数 - $ --->暴露的全局变量命名为$
当然也可以不使用内联loader
:
第一步:入口文件正常引入:
import $ from 'jquery'
console.log(window.$)
第二步:在webpack.config.js
配置文件中配置
rules: [
{
test: require.resolve('jquery'),
use: 'expose-loader?$'
}
]
第二种:
在每个模块中注入$
,注意是每个模块都有一个$
,但是这个$
并不是全局的。
在webpack.config.js
配置文件中配置
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
然后模块中可以直接使用$
第三种:
这种方式叫引入不打包。在html
文件中引入jquery
,这里以jquery
的cdn
为例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="../dist/bundle.js"></script>
</head>
<body>
</body>
</html>
注意:如果使用了这种方式,就不要在js文件内在引入jquery了,否则会重复打包。如果非要引入,那就修改一下webpack.config.js
:
module.exports = {
//...
externals: {
jquery: '$'//webpack打包时,会忽略掉jquery
},
//...
}
29、ProvidePlugin会将提供的变量挂载在window上吗
不会,官方文档没有明说,但是有句话的潜台词表明了不会。哪句话呢,友情提示:Whenever the identifier is encountered as free variable in a module...
参考官方文档
30、如何在webpack中处理图片模块
先说一下,使用图片的几种方式。
- js中创建图片来引入
- 在css中引入
- 在html文件中用img标签引入
30-1、js中创建图片来引入
使用方式:
安装包file-loader
npm install file-loader -D
js
文件内:
import logo from '../logo.png'
//file-loader 默认会在内部生成一张图片到build目录下,被导入的图片在js文件内是一个hash字符串
console.log(logo)//19470b4db4deed52a8ba081c816e8f0d.png
let image = new Image()
image.src = logo
webpack.config.js
文件内配上相应的loader
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader'
}
]
},
30-2、在css中引入
使用方式:
安装包style-loader
、css-loader
,当然还可以安装css
预处理包
npm install style-loader css-loader -D
css文件内:
body{
background: url("../logo.png")
}
webpack.config.js
文件内配上相应的loader
module: {
rules: [
{
test: /\.css$/,
loader: 'style-loader!css-loader'
}
]
},
30-3、在html文件中用img标签引入
使用方式:
安装包html-withimg-loader
npm install html-withimg-loader -D
html
文件内:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../dist/bundle.js"></script>
</head>
<body>
<img src="./logo.png" alt="">
</body>
</html>
webpack.config.js
文件内配上相应的loader
module: {
rules: [
{
test: /\.html$/,
loader: 'html-withimg-loader'
},
]
},
31、webpack打包时如何处理html的img
使用html-withimg-loader
包来处理html的img。
32、webpack打包时如何把图片变为base64
安装包url-loader
.
npm install url-loader -D
webpack.config.js
文件内配上相应的loader
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 200 * 1024
}
}
},
]
},
当图片小于设定的大小(K)的时候,用base64
来转化,否则用file-loader
产生真实的图片。
33、file-loader和url-loader的关系
他俩其实没有必然联系,只能说可以搭配起来一起工作。url-loader
只能将图片解析成base64
,当图片大小超过了限制,url-loader
就会把解析图片的工作交给其他工具(默认是file-loader
),当然,当图片大小超过了限制,而我们想用其他工具来处理图片,可以通过参数来控制:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
fallback: 'responsive-loader',//当图片大小超过8K,用responsive-loader处理
limit: 8*1024,
},
},
],
},
],
},
};
可以参考篇幅不长的官方文档
34、publicPath的作用是什么
publicPath
指定的路径会被作为前缀添加到所有的url
上。这里的url
指的是:
html
文件中的link
标签,script
标签、img
标签css
中的带有文件引入的属性等。比如:background:url()
一般当静态资源放在CDN时,publicPath
会指定CDN的路径。
官方文档
35、如何配置多页面应用
配置多页面就需要配置webpack
的多入口。
module.exports = {
mode: 'development',
entry: {
main: './src/main.js',//入口1,main.js
index: './src/index.js',//入口2,index.js
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:8].bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
chunks: 'main',//这里的main和entry里的main属性需要保持一致
filename: 'index.html'
}),
new HtmlWebpackPlugin({
template: './index.html',
chunks: 'index',//这里的index和entry里的index属性需要保持一致
filename: 'main.html'
})
]
}
36、如何在html文件指定引入某个js文件
思考中...
37、source-map
和eval-source-map
的区别
这个配置主要是debug
用的,配置选项有很多,这里挑选4个说明。
source-map
生成map
文件,定位到行列eval-source-map
不生成map
文件,定位到行列cheap-module-source-map
生成map
文件,定位到行cheap-module-eval-source-map
不生成map
文件,定位到行
38、如何实时编译打包处的文件
在webpack配置文件中新增如下配置信息:
module.exports = {
mode: 'development',
//开启实时编译
watch: true,
//实时编译的配置选项
watchOptions: {
ignored: /node_modules/,
poll:1000,//每秒询问文件变更的次数
aggregateTimeout:500//防止重复保存频繁重新编译,500毫秒内重复保存不打包
}
}
39、aggregateTimeout:500的作用是什么
当检测文件不再发生变化,会先缓存起来,等待一段时间后,再通知监听者,这个等待时间通过aggregateTimeout
配置。
40、bannerPlugin
的作用
bannerPlugin
的作用是在产出的资源文件头部添加信息,比如:添加作者、版本号等信息。
let webpack=require('webpack')
module.exports={
//...
plugins: [
new webpack.BannerPlugin('author:wangZhi')
],
//...
}
产出的文件头部如下所示:
css
/*! author:wangZhi */
body{
background:red;
}
js
/*! author:wangZhi */
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
41、webpack
如何配置代理
首先启动一个web
服务,配置如下:
var path = require('path');
module.exports = {
//...
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000
}
};
然后配置代理:
proxy: {
'/api': {//以/api开头的请求会被代理到'https://other-server.example.com'
target: 'https://other-server.example.com',
pathRewrite: {
'^/api': ''//以/api开头的路径会被替换成''
}
}
}
42、before
函数中的app
有什么作用
webpack-dev-server
是依靠express
启动一个web
服务的,配置中的app
就是express
中的app
,有了app
,我们可以编写接口,响应接口等,相当于前台自己mock
一些数据。建议看看express
官网。
module.exports = {
//...
devServer: {
before: function(app, server) {
//编写一个/some/path接口,后续请求会在这里直接处理
app.get('/some/path', function(req, res) {
res.json({ custom: 'response' });
});
}
}
};
43、webpack-dev-middleware
作用是什么
服务端启动webpack,webpack-dev-middleware
其实是一个express
的中间件。
let webpack = require('webpack')
let express = require('express')
let config = require('./webpack.config')
let middle = require('webpack-dev-middleware')
let app = express();
//webpack提供的方法,传入webpack配置,得到一个编译对象
let compiler = webpack(config);
//使用中间件
app.use(middle(compiler))
//监听端口
app.listen(2000)
44、resolve
属性有哪些配置
列出几个常用的配置:
module.exports = {
//...
resolve: {
//模块查找路径
modules: [path.resolve('node_modules'),'mydir'],
//配置别名
alias: {
bootstrap:'bootstrap/dist/css/bootstrap.css'
},
//查找字段
mainFields: ['main', 'style'],
//查找文件名
mainFiles: ['index.js'],
//查找文件的后缀名
extensions:['.js','.css','.vue']
},
};
45、如何定义别名
见上44题。
46、如何省略引入文件的后缀
见上44题。
47、如何定义环境变量
定义环境变量需要用到webpack
的一个包--->definePlugin
。
module.exports = {
//...
plugins: [
new webpack.DefinePlugin({
DEV: 'dev',
DEV_str: JSON.stringfiy('dev'),
FLAG: 'true',
FLAG_str: "'true'",
expression: '1+1',
expression_str: JSON.stringify('1+1')
})
]
}
上述配置定义了6个环境变量,打包编译后的结果为:
- DEV--->报错,
dev is not defined
- DEV_str--->
'dev'
(字符串) - FLAG--->
true
(布尔值) - FLAG_str--->
'true'
(字符串) - expression--->
2
(数字型) - expression_str--->
'1+1'
(字符串)
48、如何区分开发环境和生产环境
第一种方式,可以通过设置环境变量来区分,设置环境变量见上一题。
if(DEV==='development'){
//do something
}else if(DEV==='production'){
//do something
}
第二种方式,创建多套配置文件。
安装包 webpack-merge
npm install --save-dev webpack-merge
项目目录:
webpack-demo
|- package.json
|- webpack.common.js //开发环境、生产环境公用配置文件
|- webpack.dev.js //开发环境配置文件
|- webpack.prod.js //生产环境配置文件
|- /dist
|- /src
|- index.js
|- math.js
|- /node_modules
webpack.common.js
文件:
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js'
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Production'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
webpack.dev.js
文件:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
}
});
webpack.prod.js
文件:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
});
参考官方文档
49、webpack-merge
作用是什么
webpack-merge
的作用是合并对象。
最基本的用法:
let merge = require('webpack-merge')
let newObj = merge(obj1,obj2,obj3,...)
还是看文档吧
50、如何不解析某些依赖库
webpack
的配置文件中,通过配置externals
字段可以达到不解析某些依赖库的目的。如下:
module.exports = {
//...
externals: {
jquery: '$'//webpack打包时,会忽略掉jquery
},
//...
}
51、如何设置loader
的解析文件夹
1、直接写绝对路径
{
test: /.js$/,
use: path.resolve(__dirname,'loader/loader1.js')
}
2、配置别名
resolveLoader: {
alias: {
loader1: path.resolve(__dirname, 'loader', 'loader1')
}
}
3、配置modules
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loader')]
}
52、使用了moment
后默认会引入locale
文件夹
思考中...
53、dllPlugin
如何使用
打包一个dll文件:
let path = require('path');
//引入插件,webpack内置插件
let DllPlugin = require('webpack/lib/DllPlugin')
module.exports = {
mode: 'development',
entry: {
//将react、react-dom库打包成动态链接库
react:['react','react-dom']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].dll.js',
//动态链接库导出的全局变量
library: '_dll_[name]'
},
plugins: [
new DllPlugin({
//name需和output.library一致。
name: '_dll_[name]',
//生成的json文件存放目录
path: path.join(__dirname, 'dist', '[name].manifest.json')
})
]
}
按照如上配置,我们最终得到了一个react.dll.js
文件,该文件内容经过删减替换整理后,如下:
var _dll_react = (function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
return module.exports;
}
return __webpack_require__(0);
})({
"module1": (function (module, exports, __webpack_require__) { /* ... */}),
"module2": (function (module, exports, __webpack_require__) { /* ... */}),
"module3": (function (module, exports, __webpack_require__) { /* ... */}),
"module4": (function (module, exports, __webpack_require__) { /* ... */}),
0: (function (module, exports, __webpack_require__) {
eval("module.exports = __webpack_require__")
})
})
为了直观,我将源码中的类似./node_modules/object-assign/index.js
这样的属性名替换成了module1
,module2
等等。
分析上述代码,我们可以得知,_dll_react
变量其实就是__webpack_require__
方法。该方法接受一个模块id
,返回该模块的内容。
再来看一下,生成的react.manifest.json
文件,内容经过删减整理如下:
{
"name": "_dll_react",
"content": {
"./node_modules/react-dom/index.js": {
"id": "./node_modules/react-dom/index.js",
"buildMeta": {
"providedExports": true
}
}
//...
}
}
content
对象里的键名./node_modules/react-dom/index.js
就是模块请求路径,也就是说,当webpack
遇到了如下语句require('./node_modules/react-dom/index.js')
时,webpack
会拿着调用require
方法传入的路径去react.manifest.json
文件内的content
对象中找到键为该路径的属性,然后webpack
就获取到了该路径对应的模块内容。
如果我们不使用动态链接库,当webpack
遇到了如下语句require('./node_modules/react-dom/index.js')
时,webpack
会拿着调用require
方法传入的路径去获取文件内容,然后拼接头部信息(就是function (module, exports, __webpack_require__) {}
),然后递归解析文件内容的require语句,然后将依赖写入依赖列表。
综上所述,使用动态链接库确实在打包速度上得到了一定的提升。
使用一个dll文件相对来说就比较简单了,按照格式写就可以了:
let path = require('path');
//引入插件,webpack内置插件
let DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
},
plugins: [
//使用动态链接库
new DllReferencePlugin({
manifest:require('./dist/react.manifest.json')
})
]
}
54、如何实现多线程打包,优化前5个
安装包happypack
。
npm install --save-dev happypack
webpack.config.js
配置文件如下:
module.exports = {
rules: [
{
test: /\.js$/,
use: 'happypack/loader?id=jsx'
},
{
test: /\.less$/,
use: 'happypack/loader?id=styles'
},
],
plugins: [
new HappyPack({
id: 'jsx',
threads: 4,
loaders: ['babel-loader']
}),
new HappyPack({
id: 'styles',
threads: 2,
loaders: ['style-loader', 'css-loader', 'less-loader']
})
]
}
happypack如何工作的呢,一图胜千言:
55、tree-shaking
是否支持require
语法和有什么作用
不支持require
语法,依赖于ES2015模块的静态结构,比如:import
export
。
作用就是能够去除未用到的代码。
官方文档
56、scope hosting
的作用是什么
Scope Hoisting
可以让 Webpack
打包出来的代码文件更小、运行的更快, 它又译作 "作用域提升",是在 Webpack3
中新推出的功能。
- 代码体积更小,因为函数声明语句会产生大量代码
- 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小
在配置文件中添加一个新的插件,就可以实现scope hosting
功能。
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
}
参考文章
57、请简述webpack
整个工作流程
webpack
的打包过程比较复杂,这里用一张图简述一下,权当抛砖引玉了。
58、请简述webpack
中的HotModuleReplacement
原理。
1、当文件发生变化后,webpack
会重新打包,打包完成后,发布done
事件。
2、done
回调函数执行,通过服务端与客户端建立的长连接发送hash
值到客户端。
3、客户端收到hash
值之后,确认是否要更新。如果更新,则会通过Ajax
去请求manifest.json
文件,该文件记录了所有发生变动的模块。
4、通过manifest.json
文件,客户端使用jsonp
方式去拉取每一个变动模块的最新代码。
5、客户端更新模块,加入了3个属性:parents
、children
、hot
。
6、通过模块id
找到父模块中所有依赖该模块的回调函数并执行。
7、页面自动更新,热替换完成。
如下图所示:
59、webpack
代码分割方式有哪些,其中import()
方法的原理是什么。
第一种:多入口
module.exports = {
entry: {
page1: './src/page1.js',
page2: './src/page2.js',
page3: './src/page3.js'
}
}
第二种:webpack内部配置项。
module.exports = {
optimization:{
splitChunks:{
cacheGroups:{
vendors:{
chunks:'initial',//指定分割的类型,默认3种选项 all async initial
name:'vendors',//给分割出去的代码块起一个名字叫vendors
test:/node_modules/,//如果模块ID匹配这个正则的话,就会添加一vendors代码块
priority:-10 //优先级
},
commons:{
chunks:'initial',
name:'commons',
minSize:0,//如果模块的大小大于多少的话才需要提取
minChunks:2,//最少最几个chunk引用才需要提取
priority:-20
}
}
}
}
}
第三种:调用webpack
提供的import
方法。
假设,项目的入口文件是index.js
,内容如下:
let button = document.createElement('button');
button.innerHTML = '点我点我';
button.addEventListener('click',event=>{
debugger;
import('./hello.js').then(result=>{
alert(result.default);
});
});
document.body.appendChild(button);
入口文件index.js
被编译后的代码如下:
let button = document.createElement('button');
button.innerHTML = '点我点我';
button.addEventListener('click', event => {
__webpack_require__.e("src_hello_js.js").then(__webpack_require__.t.bind(__webpack_require__, "./src/hello.js")).then(result => {
alert(result.default);
});
});
document.body.appendChild(button);
看重点代码import('./hello.js')
被编译成了__webpack_require__.e("src_hello_js.js").then(__webpack_require__.t.bind(__webpack_require__, "./src/hello.js"))
,./hello.js
会被当作为一个入口文件,然后打包成一个独立的文件,和项目入口文件index.js
打包出来的文件放在一起。也就是说,我们这里的项目本来就一个入口文件,结果打包出来两个文件,原因就是使用了import
方法将代码进行了分割。
__webpack_require__.e
方法代码如下:
__webpack_require__.e = function (chunkId) {
return new Promise((resovle, reject) => {
installedChunks[chunkId] = resovle;
let script = document.createElement('script');
script.src = chunkId;
document.body.appendChild(script);
}).catch(error => {
alert('异步加载失败');
});
}
可以看到该方法主要功能就是根据传入的chunkId
使用jsonp
去拉取对应的模块代码。这里它返回了一个promise
,并将resolve
放到了全局installedChunks
对象上。因为这里不能确定jsonp
什么时候成功,所以无法调用resolve
,只能将它挂载到全局变量中。那什么时候能确定jsonp
成功了呢,答案是在jsonp
的回调函数里可以确定,也就是下面的webpackJsonp
方法里。
先来看看jsonp
拉取回来的代码长什么样子吧,如下:
window.webpackJsonp("src_hello_js.js", {
"./src/hello.js": (function (module, exports, __webpack_require__) {
module.exports = 'hello';
}),
});
jsonp
标准格式,返回一个方法调用,参数就是响应的数据。
window.webpackJsonp
方法代码如下:
window.webpackJsonp = (chunkId, moreModules) => {
for (moduleId in moreModules) {
modules[moduleId] = moreModules[moduleId];
}
installedChunks[chunkId]();//resolve()
installedChunks[chunkId] = 0;
}
webpackJsonp
主要工作就是将拉取回来的模块一一挂载到全局的modules
对象中。并且改变对应的promise
状态,也即是执行事先放在全局变量中的resolve
方法,这样就会执行之后的then
方法了。
__webpack_require__.t
方法代码如下:
__webpack_require__.t = function (value) {
value = __webpack_require__(value);
return {
default:
value
};
}
该方法就是简单的去加载模块,然后返回加载后的结果。
一图胜千言: