本篇文章解答了60多道关于webpack
的问题,答案有详细有简单,时间关系,有的问题还未作出解答,后续会补上,持续更新。若是答案有不对的地方,请轻拍。javascript
全局安装webpack
,意味着webpack
包被安装到npm的全局包目录里了。
查看npm
全局包位置:css
npm root -g
复制代码
附加点npm
的知识:
html
查看npm
配置信息:vue
npm config ls
复制代码
查看npm
下载源:java
npm config get registry
复制代码
查看npm
安装目录:node
npm config get prefix
复制代码
这里假设经过命令npm config get prefix
获取到的路径为C:\Users\wz\AppData\Roaming\npm
,为了方便起见,用A
代替C:\Users\wz\AppData\Roaming
经过这个目录能够获取到如下信息:
react
npm
全局配置文件(A\npm\etc
)A\npm\node_modules
)A\npm
)A\npm-cache
)本地安装webpack
,webpack
包被安装到了项目根目录下的node_modules
里了。jquery
开发依赖,指的是只有在项目开发、编译、打包、压缩等过程才会用到的包,一旦文件成功产出,就再也不须要他们了,好比:less-loader
、webpack
等等。webpack
项目依赖,指的是从开始编写项目到项目上线之后都须要的包,好比:vue
、axios
等等。ios
须要安装:
webpack
(webpack
核心包)webpack-cli
(webpack
命令行工具,依赖于webpack
核心包)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);
}, []);
//...
复制代码
为何会在浏览器里执行,就要从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(/* ... */)
})
})
复制代码
分析以上代码:
一、整体是一个自执行函数,双括号形式。
(function(modules){/* ... */})({})
复制代码
一个自执行函数固然能够在浏览器中执行了。
二、自执行函数的函数体内,定义了模块缓存对象installedModules
,定义了模块加载方法__webpack_require__
,这个__webpack_require__
方法就是为浏览器量身打造的,做用至关于node
中的require
方法。__webpack_require__
方法的逻辑也不难理解,首先经过模块名(其实就是一个路径)去模块缓存对象上查找,找不到的话,就新建一个模块module
并缓存到installedModules
上,而后经过模块名找到对应的模块函数,执行它,并将module
等参数传入,最后返回模块导出对象module.exports
。这段代码建议仔细看看
三、自执行函数的参数。该参数是一个对象,相似下面这样:
{
"./src/index.js": (function (module, exports) {eval(/* ... */)})
}
复制代码
该对象的键是一个路径字符串,其实就是咱们调用require方法时传入的模块路径; 值为一个接收
module
和exports
参数的函数,函数体内是一个包裹着一堆字符串代码的eval
函数,这一堆字符串代码就是咱们写的代码。可见,webpack
为了让咱们的代码可以在浏览器里执行,作了多少工做。
webpack
有几种模式,对应模式的做用webpack
有3种模式:
development
production
none
简单来讲,不一样的模式下,webpack会启用不一样的插件,或者不一样的优化手段。
development
模式下,会启用如下插件:
production
模式下,会启用如下插件:
none
模式下,不会启动任何插件。 详情可参考这篇文章
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
复制代码
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
的启动目录,以下:
使用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
的更多信息,这里
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>
复制代码
loader
的做用是对源代码进行转换,webpack
只认识js
、json
2种文件,其余类型的文件,好比css
、img
、less
等等,只能依靠loader
去解析转换了。
官方解释,这里
css-loader
主要处理css
文件中@import
和url()
语法的。官方文档
style-loader
主要做用是将css
样式以style
标签的形式插入到页面中。官方文档
loader
要返回js
脚本loader
只作一件事情,为了使loader
在更多场景下链式调用loader
都是一个node
模块loader
有同步的,也有异步的loader
有两个执行阶段,pitch
、normal
官网文档
loader
的执行顺序比较讲究, 以下图所示:
配置文件写法、行内loader
写法、命令行写法3种
配置文件写法:
就是将配置信息写到webpack.config.js
,写法又有如下几种:
loader
module.exports={
module:{
rules:[
{
test: /.js$/,
loader: 'my-loader',
exclude: /node_modules/
},
]
}
}
复制代码
use
,字符串形式module.exports={
module:{
rules[
{
test: /.js$/,
use: 'my-loader',//直接传递字符串
exclude: /node_modules/
},
]
}
}
复制代码
use
,对象形式module.exports={
module:{
rules[
{
test: /.js$/,
use: { //对象形式,能够给loader传递参数
loader:'my-loader',
options:{}//这里传递参数给loader
}
exclude: /node_modules/
},
]
}
}
复制代码
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'
复制代码
从右向左,从下到上
{
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
第一种:在配置文件中传递参数
module.exports={
module:{
rules[
{
test: /.js$/,
use: { //对象形式,能够给loader传递参数
loader:'my-loader',
options:{}//这里传递参数给loader
}
exclude: /node_modules/
},
]
}
}
复制代码
第二种:行内loader
传递参数的方法
let foo = require('expose-loader?$!./a.js')
复制代码
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
思考中...
css
预处理器:less
、sass
、stylus
对应的loader
:less-loader
、sass-loader
、stylus-loader
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
更多用法
安装包:
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/
},
]
}
复制代码
安装包:
npm install optimize-css-assets-webpack-plugin -D
复制代码
webpack.config.js
配置:
{
plugins: [
new OptimizeCSSAssetsPlugin()
]
}
复制代码
安装包:
npm install uglifyjs-webpack-plugin --save-dev
复制代码
webpack.config.js
配置:
module.exports = {
optimization: {
minimizer: [ new UglifyJsPlugin() ],
},
};
复制代码
或者:
{
plugins: [
new UglifyJsPlugin()
]
}
复制代码
uglifyjs-webpack-plugin
官方文档
npm install @babel/core @babel/preset-env babel-loader -D
复制代码
@babel/core
是核心包,提供基础的API服务。babel-loader
依赖@babel/core
关于babel的知识,推荐阅读这两篇:
我是第一篇
我是第二篇
@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
是用来转换修饰符(@
)的。
官方文档
@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题的两篇文章。
可使用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
官网文档
暴露变量其实就是暴露到全局,也就是挂在到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
},
//...
}
复制代码
不会,官方文档没有明说,可是有句话的潜台词代表了不会。哪句话呢,友情提示:Whenever the identifier is encountered as free variable in a module...
参考官方文档
先说一下,使用图片的几种方式。
使用方式:
安装包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'
}
]
},
复制代码
使用方式:
安装包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'
}
]
},
复制代码
使用方式:
安装包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'
},
]
},
复制代码
使用html-withimg-loader
包来处理html的img。
安装包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
产生真实的图片。
他俩其实没有必然联系,只能说能够搭配起来一块儿工做。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,
},
},
],
},
],
},
};
复制代码
能够参考篇幅不长的官方文档
publicPath
指定的路径会被做为前缀添加到全部的url
上。这里的url
指的是:
html
文件中的link
标签,script
标签、img
标签css
中的带有文件引入的属性等。好比:background:url()
通常当静态资源放在CDN时,publicPath
会指定CDN的路径。
官方文档
配置多页面就须要配置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'
})
]
}
复制代码
思考中...
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
文件,定位到行在webpack配置文件中新增以下配置信息:
module.exports = {
mode: 'development',
//开启实时编译
watch: true,
//实时编译的配置选项
watchOptions: {
ignored: /node_modules/,
poll:1000,//每秒询问文件变动的次数
aggregateTimeout:500//防止重复保存频繁从新编译,500毫秒内重复保存不打包
}
}
复制代码
当检测文件再也不发生变化,会先缓存起来,等待一段时间后,再通知监听者,这个等待时间经过aggregateTimeout
配置。
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) {
复制代码
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开头的路径会被替换成''
}
}
}
复制代码
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' });
});
}
}
};
复制代码
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)
复制代码
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']
},
};
复制代码
见上44题。
见上44题。
定义环境变量须要用到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 is not defined
'dev'
(字符串)true
(布尔值)'true'
(字符串)2
(数字型)'1+1'
(字符串)第一种方式,能够经过设置环境变量来区分,设置环境变量见上一题。
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',
});
复制代码
参考官方文档
webpack-merge
做用是什么webpack-merge
的做用是合并对象。
最基本的用法:
let merge = require('webpack-merge')
let newObj = merge(obj1,obj2,obj3,...)
复制代码
仍是看文档吧
webpack
的配置文件中,经过配置externals
字段能够达到不解析某些依赖库的目的。以下:
module.exports = {
//...
externals: {
jquery: '$'//webpack打包时,会忽略掉jquery
},
//...
}
复制代码
loader
的解析文件夹一、直接写绝对路径
{
test: /.js$/,
use: path.resolve(__dirname,'loader/loader1.js')
}
复制代码
二、配置别名
resolveLoader: {
alias: {
loader1: path.resolve(__dirname, 'loader', 'loader1')
}
}
复制代码
三、配置modules
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loader')]
}
复制代码
moment
后默认会引入locale
文件夹思考中...
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')
})
]
}
复制代码
安装包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如何工做的呢,一图胜千言:
tree-shaking
是否支持require
语法和有什么做用不支持require
语法,依赖于ES2015模块的静态结构,好比:import
export
。 做用就是可以去除未用到的代码。
官方文档
scope hosting
的做用是什么Scope Hoisting
可让 Webpack
打包出来的代码文件更小、运行的更快, 它又译做 "做用域提高",是在 Webpack3
中新推出的功能。
在配置文件中添加一个新的插件,就能够实现scope hosting
功能。
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
}
复制代码
参考文章
webpack
整个工做流程webpack
的打包过程比较复杂,这里用一张图简述一下,权当抛砖引玉了。
webpack
中的HotModuleReplacement
原理。一、当文件发生变化后,webpack
会从新打包,打包完成后,发布done
事件。
二、done
回调函数执行,经过服务端与客户端创建的长链接发送hash
值到客户端。
三、客户端收到hash
值以后,确认是否要更新。若是更新,则会经过Ajax
去请求manifest.json
文件,该文件记录了全部发生变更的模块。
四、经过manifest.json
文件,客户端使用jsonp
方式去拉取每个变更模块的最新代码。
五、客户端更新模块,加入了3个属性:parents
、children
、hot
。
六、经过模块id
找到父模块中全部依赖该模块的回调函数并执行。
七、页面自动更新,热替换完成。
以下图所示:
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
};
}
复制代码
该方法就是简单的去加载模块,而后返回加载后的结果。
一图胜千言: