webpack 从入门到工程实践

本文较长,为了节省你的阅读时间,在文前列写做思路以下:css

什么是webpack,它要解决的是什么问题?
对webpack的主要配置项进行分析,虽然不会涉及太多细节,可是期待在本节能让咱们知晓若是咱们有什么需求,咱们该从哪些配置项着手修改?
分析create-react-app的基础配置文件。
分享一些本身工做中对webpack的实践。
本文的初衷是和你一块儿理清webpack的使用逻辑,以便能更加容易的编写及拓展本身项目所需的配置文件。不过也得提早说明本文可能并非一篇好的能够跟着操做的教程(想跟着一步步作的童鞋能够看官方示例和webpack入门,看这篇就够了。html

换个角度看待webpack前端

近年来,前端技术蓬勃发展,咱们想在js更方便的实现html , 社区就出现了jsx,咱们以为原生的css不够好用,社区就提出了scss,less,针对前端项目愈来愈强的模块化开发需求,社区出现了AMD,CommonJS,ES2015 import等等方案。遗憾的是,这些方案大多并不直接被浏览器支持,每每伴随这些方案而生的还有另一些,让这些新技术应用于浏览器的方案,咱们用babel来转换下一代的js,转换jsx;咱们用各类工具转换scss,less为css;咱们发现项目愈来愈复杂,代码体积愈来愈大,又要开始寻找各类优化,压缩,分割方案。前端工程化这个过程,真是让咱们大费精力。咱们也大可能是在寻找前端模块化解决方案的过程当中知晓了webpack。node

的确,webpack的流行得益于野性生长的前端,其本质是一种前端模块化打包解决方案,可是更重要的是它又是一个能够融合运用各类前端新技术的平台,明白webpack的使用哲学后,只须要简单的配置,咱们就能够为所欲为的在webpack项目中使用jsx/ts,使用babel/postcss等平台提供的众多其它功能,只需经过一条命令由源码构建最终可用文件。能够不夸张的说webpack为前端的工程化开发提供了一套相对容易和完整的解决方案。一些知名的脚手架工具,也大多基于webpack(好比create-react-app)。react

webpack好难!我第一次复制别人的配置文件到个人项目中,发现以本身仅有的JS知识彻底看不懂时,也有这种感受。后来发现有这种感受实际上是由于本身看待webpack的角度错了,对大多数前端开发者而言,以往咱们接触的各类库,要么相似jQuery,经过$符在前端项目中直接运行,所作的事情只在前端生效,要么相似express.js,在node.js项目中直接require后就可使用,所作的事情只在后端生效。webpack的不一样之处就在于,虽然咱们的配置文件位于前端项目中,但实际上它却运行于node.js,以后的处理结果又供前端使用(也可能供node使用)。因此学习以前,咱们转变一下思惟,从node.js的角度来看webpack,不少事情就会简单起来。jquery

咱们对下图必定不陌生,假设如今咱们手中有一系列相互关联的文件js,jsx,css,less,jpg,咱们一步步的看看为了把它们转换为项目最终须要的,浏览器可识别的文件,webpack都作了什么。webpack

webpack作了什么es6

对webpack主要配置项的分析web

若是不去考究细节,咱们大可把webpack简化理解为一个函数,配置文件则是其参数,传入合理的参数后,运行函数就能获得咱们想要的结果。正则表达式

webpack也只是一个打包工具,它可不是什么智能ai,咱们该从哪儿输入文件,咱们想把输出结果放哪里,输出结果应该长什么样,它都不知道。而咱们目前和webpack函数交互的惟一方法就是经过参数,这就涉及到webpack配置对象中两个重要概念entry和output了,所以,咱们的配置对象至少具有如下结构:

// 第一阶段
{
entry:{},
output:{}
}
入口配置entry

理想状态是,咱们把全部本身编写的文件都交给webpack,让它找明里面的关系,进过必定处理后,给出最终咱们想要的结果。遗憾的是,webpack也不会机械学习,咱们手头的一堆文件之间的关系是本身肯定的,通常咱们的项目都会存在一个或几个主文件,其它的全部的文件(模块)都直接或间接的连接到了这些文件。咱们在entry项中须要填写的就是这些主文件的信息。

不过咱们也不要嫌弃webpack笨,经过咱们给的主文件路径,经过分析它能构建最合适的依赖关系,这意味着只有用过的代码才会被打包,好比咱们在一个文件中写了五个模块,可是实际只用了其中一个,打包后的代码只会包含引用过的模块。

webpack中不少地方的配置都有多种写法,这也是其让人疑惑的地方之一,很遗憾,咱们的第一个配置对象entry就是如此。

entry能够是三种值:

字符串:如entry:'./src/index.js',字符串也能够是函数的返回值,如entry: () => './demo',单一入口占位符name值为main(关于占位符,稍后详述);

数组形式,如[react,react-dom],能够把数组中的多个文件打包转换为一个chunk;

对象形式,若是咱们须要配置的是多页应用,或者咱们要抽离出指定的模块作为公共代码,就须要采用这种形式了,属性名是占位符name的值,属性值能够是上面的字符串和数组,以下:

// 值得注意的是入口文件有几个就会生成几个独立的依赖图谱。
entry:{
main:'./src/index.js',
second:'./src/index2.js',
vendor: ['react','react-dom']
}
好吧,千辛万苦,咱们在一堆各类类型的文件中找到了入口文件,这里咱们假设为./src/index.js,此时咱们的配置对象以下:

// 第二阶段
{
entry:{
main:'./src/index.js'
},
output:{}
}
webpack依据入口文件来构建依赖体系,每一个入口文件在打包完成后都具有其独立的依赖图谱,在此咱们暂时称这些由主入口配置生成的文件为主js文件。
输出配置output

output配置项做用于打包文件的输出阶段,其做用在于告知webpack以何种方式输出打包文件,关于output,webpack提供了众多的可配置选项,咱们简单介绍下最经常使用的选项。

output基本配置项

咱们都另存过文件,当咱们另存一个文件时,咱们须要肯定另存的文件名和另存的路径,webpack将打包后的结果导出的过程就相似于此,此过程由output配置项控制,其最基本配置包括filename和path两项。这两项用以决定上述主js文件的存储行为。

不过咱们程序的首页每每不需用到某个主js文件的全部代码,实际开发中,咱们经常使用必定方法对代码进行分割,方便按需加载,提高体验。这类不具有独立依赖的文件,咱们称之为chunkfile。chunkfile的命名,在output中对应chunkFilename项;

此外output的publicPath项,用于控制打包文件的相对或者绝对引用路径,配置不当每每形成在运行时找不到文件。

咱们补充配置对象中output的配置,以下:

// 第三阶段
{
entry:{
main:'./src/index.js'
},
output:{
path: path.join(__dirname,'./dist'),
name:'js/bundle-name-[hash].js',
chunkFilename:'js/name.chunk.js',
publicPath:'/dist/'
}
}
上述代码中用到了占位符name,咱们对占位符作统一解释:

webpack中常见的占位符有多种,常见的以下:

[id]:webpack给块分配的内部chunk id,若是你没有隐藏,你能在打包后的命令行中看到;
[hash]:每次构建过程当中,生成的惟一 hash 值;
[chunkhash]: 依据于打包生成文件内容的 hash 值,内容不变,值不变;

output其它配置

output配置项生效于保存这个过程,除了上面的基本配置,若是你想对这个阶段的打包文件进行更改,均可在此配置项中进行相关设置。

好比output提供了众多关于hash的属性,让咱们对[hash]占位符的值有更加精细的控制,如生成方式,使用的算法,预设的长度等等;如chunkLoadTimeout属性则容许咱们设置chunk文件的请求超时时间。

工具都是依赖于需求来使用的,若是你此阶段有别的需求,可点击更多配置寻找解决方案。

咱们已经知道了webpack中基本的输入和输出配置,可是webpack对各模块的处理过程,目前为止,对咱们仍是一个谜。考虑到webpack执行于node.js环境,其自己只能理解js文件,而咱们输入的倒是一大堆不一样格式的文件,毫无疑问,要作的第一件事情是对各种模块进行处理,这就涉及到webpack中第三个重要配置对象了---module。

对模块的处理:module的配置

使用webpack时,咱们经常据说,对webpack而言,全部的文件都是模块,前文中我也经常混用模块和文件,不过本质上模块和文件仍是不一样的,webpack里,文件能够当作模块,而模块却不必定是一个独立的文件。咱们先看看webpack内置支持的模块类型:

ES2015 import(webpack2开始内置支持)。

CommonJS require。

AMD define和require语句。

css/less/sass 中的@import。

样式中的url(user-gold-cdn.xitu.io/2017/11/23/…

咱们知道webpack只能处理js文件,咱们的浏览器也可能不支持一些最新的js语法,基于此,咱们须要对传入的模块进行必定的预处理,这就涉及到webpack的又一核心概念 --- loader,使用loader,webpack容许咱们打包任何JS以外的静态资源。

loader的做用和基本用法

webpack中,loader的配置主要在module.rules中进行,module.rules是一个数组,咱们能够把每一项看作一个Rule,每一个Rule主要作了如下两件事:

识别文件类型,以肯定具体处理该数据的loader,(Rule.test属性)。

使用相关loader对文件进行相应的操做转换,(Rule.use属性)。

还记得前面咱们说过,咱们手头的文件类型有js,jsx,css,less,jpg吗?咱们看看在webpack中该如何处理和转换它们。

注:如下loader使用前需经过npm/cnpm/yarn安装:

module: {
rules: [{
test: /(.jsx|.js)$/,
use: {
loader: "babel-loader",
options: {
presets: ["es2015", "react"]
}
},
exclude: /node_modules/
}, {
test: /.css$/,
use: ["style-loader", "css-loader"]
}, {
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"]
}]
},
这就是webpack中loader的基本用法了,在module.rules数组中进行配置便可,module.rules是一个数组,里面每一项(一个Rule)表示以必定的规则匹配和处理某种或某几种类型的文件。具体说来:

Rule.test:表示匹配规则,它是一个正则表达式。

Rule.use:表示针对匹配的文件将使用的处理loader,其值能够是字符串,数组和对象,当是对象形式时,咱们可使用options等命令进行进一步的配置。

Rule中的其它一些规则也大多围绕匹配条件和应用结果展开,如Rule.exclude和Rule.include表示应该匹配或不该该匹配某资源;Rule.oneOf表示对该资源只应用第一个匹配的loader;Rule.enforce则用于指定loader的种类。

loader能够作什么

webpack的强大之处在于,能够轻松在其中应用其它平台提供的功能,好比说babel,postcss自己都是独立的平台。在webpack中只须要添加babel-loader和postcss-loader就可使用。这两个平台自己也提供众多的配置项,默认分别可在.babelrc 和postcss.config.js中完成,webpack并不影响这些配置文件的使用。不过须要说明的可能不少童鞋是在学习webpack时才接触这两个平台,致使在这两个平台上遇到的问题误觉得是webpack的问题。
除了上述的转换编译,经过loader,webpack还容许咱们实现如下功能:

转换编译:script-loader/babel-loader/ts-loader/coffee-loader等。

处理样式:style-loader/css-loader/less-loader/sass-loader/postcss-loader等。

处理文件:raw-loader/url-loader/file-loader/等。

处理数据:csv-loader/xml-loader等。

处理模板语言:html-loader/pug-loader/jade-loader/markdown-loader等。

清理和测试:mocha-loader/eslint-loader等。

关于各个loader更详细的介绍,可点击loaders查看。

module.noParse

关于module,另外一个经常使用的配置项为module.noParse,经过它,咱们在构建过程当中能够忽略大型的 library 以提升构建效率。

咱们来整理一下此阶段,咱们的配置对象代码,以下:

// 第四阶段
{
entry: {
main: './src/index.js'
},
output: {
path: path.join(__dirname, './dist'),
name: 'js/bundle-name.js',
chunkFilename: 'js/name.chunk.js',
publicPath: '/dist/'
},
module: {
rules: [{
test: /(.jsx|.js)$/,
use: {
loader: "babel-loader",
options: {
presets: ["es2015", "react"]
}
},
exclude: /node_modules/
}, {
test: /.css$/,
use: ["style-loader", "css-loader"]
}, {
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"]
}]
}
}
进过这一阶段的处理,咱们的代码其实已经能够输出使用了。不过这样的输出可能还不能让人满意,咱们想要抽离公共代码;咱们想统一修改全部代码中的某些值;咱们还想对代码进行压缩,去除全部的console… , 总之这一阶段的代码仍是存在很大的改进空间的,这就是plugin的用武之地了。

plugins的配置

webpack称plugins为其backbone,一切loader不能作的处理均可由plugins来作。此评价足见其重要性。

鉴于插件如此重要,webpack内置了众多的经常使用的plugins,无需额外安装就可直接使用。咱们先看看plugins的基本配置方法,而后再分类介绍一下经常使用的plugins。

plugins的使用方法

plugins是一个数组,数组中的每一项都是某一个plugin的实例,plugins数组甚至能够存在一个插件的多个实例。

下面代码中,分别展现了webpack内置插件和第三方插件的使用方法:

// 第三方插件须要在安装后引入
const CleanWebpackPlugin = require("clean-webpack-plugin");

{
user-gold-cdn.xitu.io/2017/11/23/…
plugins:[
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
}
}),
new CleanWebpackPlugin(["js"], {
root: __dirname + "/stu/",
verbose: true,
dry: false
})
]
}
一种插件其实就是一种函数,经过传入不一样的参数,插件可按咱们的需求实现不一样的功能。不过插件数量众多,咱们甚至还能够本身来写插件,每一个插件还有本身特定的配置规则,这也是webpack让人以为难学的地方之一,不过好在做为一个工具,对于咱们大多数人最须要掌握的plugins并非那么多,其它的待真的有相关需求再边查边学也不迟,webpack的插件列表可参看这里。

经常使用plugins的介绍

plugins功能众多,可是大多数plugin的功能主要集中在两方面:

对前一阶段打包后的代码进行处理,如添加替换一些内容,分割代码为多块,添加一些全局设置等。

辅助输出,如自动生成带有连接的index.html,对生成文件存储文件夹作必定的清理等。

对代码进行处理

BannerPlugin:给代码添加版权信息,如在plugins数组中添加new BannerPlugin(‘GitChat’)后能在打包生成的全部文件前添加注释GitChat详见。

CommonsChunkPlugin,用于抽离代码,具备多种用途 详情查看CommonsChunkPlugin。

抽离不一样文件的共享代码,减小chunk间的重复代码,有效利用缓存。

抽离可能整个项目都在使用的第三方模块,好比react react-dom。

将多个子chunk中的共用代码打包进父chunk或使用异步加载的单独chunk。

抽离Manifest这类每次打包都会变化的内容,减轻打包时候的压力,提高构建速度。

CompressionWebpackPlugin:使用配置的算法(如gzip)压缩打包生成的文件,详见。

DefinePlugin:建立一个在编译时可配置的全局常量,若是你自定义了一个全局变量PRODUCTION,可在此设置其值来区分开发仍是生产环境详见。

EnvironmentPlugin:其实是DefinePlugin插件中对process.env进行设置的简写形式,如new webpack.EnvironmentPlugin(['NODE_ENV', 'DEBUG'])将设置process.env.NODE_ENV='DEBUG',EnvironmentPlugin。

ExtractTextWebpackPlugin:抽离css文件为单独的css文件,详见。

ProvidePlugin:全局自动加载模块,如添加new webpack.ProvidePlugin({$: 'jquery', jQuery: 'jquery'})后,则全局不用在导入jquery就能够直接使用$,ProvidePlugin。

UglifyjsWebpackPlugin:使用前须要先安装,基于UglifyJS压缩代码,支持其全部配置UglifyjsWebpackPlugin。

辅助输出打包后的代码

HtmlWebpackPlugin:使用前须要先安装,为你自动生成一个html文件,该文件将自动依据entry的配置引入依赖,若是你的文件名中添加了[hash]等占位符,这将很是有用, 详见。

CleanWebpackPlugin:使用前须要先安装,此插件容许你在配置之后,每次打包时,清空所配置的文件夹,若是你每次打包的文件名不一样,这将很是有用 GitHub - clean-webpack-plugin。

经过上述对不一样插件的描述,你必定大体明白了,插件能够作什么,以后在开发的过程当中,若是你遇到的什么须要在此阶段解决的问题,大可搜索看看是否有相关的插件,推荐查阅awesome-webpack。

学习了插件之后,如今咱们的配置对象是以下这样:

// 第5阶段
{
entry: {
main: './src/index.js'
},
output: {
path: path.join(dirname, './dist'),
name: 'js/bundle-name.js',
chunkFilename: 'js/name.chunk.js',
publicPath: '/dist/'
},
module: {
rules: [{
test: /(.jsx|.js)$/,
use: {
loader: "babel-loader",
options: {
presets: ["es2015", "react"]
}
},
exclude: /node_modules/
}, {
test: /.css$/,
use: ["style-loader", "css-loader"]
}, {
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"]
}]
},
plugins: [
new webpack
.optimize
.CommonsChunkPlugin({
name: 'vendor',
filename: "js/name-[chunkhash].js"
}),
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
new webpack.ProvidePlugin({
Promise: "exports-loader?global.Promise!es6-promise",
fetch: "exports-loader?self.fetch!whatwg-fetch"
}),
new HtmlWebpackPlugin({
filename: "index.html",
template: "app/index.html",
inject: "body"
}),
new CleanWebpackPlugin(["js"], {
root:
dirname + "/stu/",
verbose: true,
dry: false
}),
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
}
})
]
}
至此,从输入entry->处理loaders/plugins->输出output,咱们讲解了webpack的核心功能,不过webpack还提供其它的一些配置项,这些配置项大多从两方面起做用,辅助开发、对构建过程当中的一些细节作调整。对这些属性,下面只作简单的介绍。

其它的一些配置

辅助开发的相关属性

devtool:

打包后的代码和原始的代码每每存在较大的差别,此选项控制是否生成,以及如何生成 source map,用以帮助你进行调试,详情可查看Devtool。

devServer:

经过配置devServer选项,你能够开启一个本地服务器,webpack为此本地服务器提供了很是多的配置选项,点击查看dev-server,你会发现经过合适的配置,你能够拥有全部本地服务器可提供的功能。

watch:

启用 Watch 模式后,webpack 将持续监放任何已解析文件的更改,从新构建文件,Watch 模式默认关闭,在开发时候若是开启会很方便。

watchOptions:

一组用来定制 Watch 模式的选项: 详见 watch。

performance:

本配置让你设置打包后命令行中该如何展现性能提示,好比是否开启提示,资源若是超过某个大小时该警告仍是报错,详见performance。

stats:

本选项让你配置打包过程当中输出的内容,如没有输出none,标准输出normal,所有输出verbose,只输出错误errors-only等等。

精细配置相关属性

content:设置基础路径,默认使用当前目录。

resolve:

肯定模块如何被解析,webpack已经提供了合理的默认值,不过经过你的自定义配置,能够对模块解析实现更加精细的控制,如对某些经常使用模块能够经过设置别名以更容易引用,也可在此处设置可被忽略的后缀名,详见 resolve。

target:

告知 webpack 须要打包的代码执行的环境,针对 node 和 web 打包过程会有所不一样,详见Target。

externals:

让打包生成的代码中不添加某依赖项,而让这些依赖项直接从用户环境中获取,在进行库的开发时很是有用。

node:

是一个对象,其中每一个属性都是 Node.js 全局变量或模块的名称,每一项的设置值均可以是(true/mock/empty/false)中的一种,以肯定这些node中的对象在其它环境中是否可用。

此外webpack还具有其它一些用的比较少的配置对象,详见 Other Options。

至此,咱们了解了webpack经常使用的配置项及其意义。为了检测咱们的学习成果,咱们一块儿分析一个中等项目中的webpack配置文件。配置文件来自于create-react-app,使用create-react-app新建项目后,执行npm run eject可看到多个配置文件,这里咱们选择webpack.dev.js。

分析create-react-app中webpack的配置

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');

module.exports = {
devtool: 'cheap-module-source-map',
entry: [
require.resolve('react-dev-utils/webpackHotDevClient'),
require.resolve('./polyfills'),
require.resolve('react-error-overlay'),
'src/index.js'
],
output: {
path: '/build/',
pathinfo: true,
filename: 'static/js/bundle.js',
chunkFilename: 'static/js/name.chunk.js',
publicPath: '',
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath).replace(/\/g, '/'),
},
resolve: {
modules: ['node_modules'],
extensions: ['.web.js', '.js', '.json', '.web.jsx', '.jsx'],
alias: {
'react-native': 'react-native-web',
},
plugins: [
new ModuleScopePlugin('/src'),
],
},
module: {
strictExportPresence: true,
rules: [{
test: /.(js|jsx)$/,
enforce: 'pre',
use: [{
options: {
formatter: eslintFormatter,
},
loader: require.resolve('eslint-loader'),
}, ],
include: 'src',
}, {
exclude: [/.html$/,/.(js|jsx)$/,/.css$/,/.json$/,/.bmp$/,/.gif$/,/.jpe?g$/,/.png$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/media/name.[hash:8].ext',
},
}, {
test: [/.bmp$/, /.gif$/, /.jpe?g$/, /.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/name.[hash:8].ext',
},
}, {
test: /.(js|jsx)$/,
include: 'src',
loader: require.resolve('babel-loader'),
options: {
cacheDirectory: true,
},
}, {
test: /.css$/,
use: [
require.resolve('style-loader'), {
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
}, {
loader: require.resolve('postcss-loader'),
options: {
user-gold-cdn.xitu.io/2017/11/23/…
},
},
],
}, ],
},
plugins: [
new InterpolateHtmlPlugin({
NODE_ENV:'development',
PUBLIC_URL:''
}),
new HtmlWebpackPlugin({
inject: true,
template: 'public/index.html',
}),
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin({
'process.env':{
NODE_ENV:"development",
PUBLIC_URL:'" "'
}
}),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
new webpack.IgnorePlugin(/^.\/locale$/, /moment$/),
],
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
},
performance: {
hints: false,
},
};
对可能和你看到的webpack.config.dev.js有所不一样的说明::

npm run reject以前,对create-react-app的一些设置会影响这里看到的配置文件。

原始的webpack.config.dev.js中,部分值由外部函数生成,相关值,在上述代码中直接改成了肯定的结果,如env.raw在上述代码中被替换为:

{
NODE_ENV:'development',
PUBLIC_URL:''
}
create-react-app在开发环境并不生成真实的文件到硬盘,上述代码中的部分路径可能有误,见谅。

推荐在看下面的分析前,花三分钟看看上述文件,若是都能看得懂,那么恭喜你,你已经明白webpack的运做方式了,快去本身的项目中实践吧,若是还有疑惑,也没关系,咱们一块儿来分析。

webpack.config.dev.js执行于node环境

首先,咱们应该明确webpack.config.dev.js执行于node环境,目的在于返回webpack须要的配置对象,所以其中可使用node提供的一些特殊变量和语法,好比__dirname,又如引入模块时采用CommonJS模式。

此文件的开头,首先经过require语句引入了path,webpack和一系列webpack插件,除了HtmlWebpackPlugin在前文中咱们见过,其它的咱们都不曾见过,其实这些大可能是create-react-app针对webpack已有的插件改进或新开发的插件,因此不熟悉也正常,随后咱们将一个个的弄清楚它们是干吗的。

对module.exports的分析

devtool

此处的配置值为cheap-module-source-map,表明不带列映射的 SourceMap,将加载的 Source Map 简化为每行单独映射。

entry

此处的entry是一个数组,表明着四项的代码都会添加到打包结果之中。

webpackHotDevClient能够被看作具备更好体验的WebpackDevServer。

./ployfill.js用以在浏览器中支持promise/fetch/object-assign。

react-error-overlay在开发环境中使用,强制显示错误页面。

./src/index.js则是咱们的app的主入口。

output

在实际使用create-react-app的过程当中,咱们并看不见开发环境的打包结果,所以此处的说明仅供参考。

path指定,打包后文件存放的位置为/build/。

pathinfo为true,在打包文件后,在其中所包含引用模块的信息,这在开发环境中有利于调试。

filename指定了打包的名字和基本的引用路径static/js/bundle.js。

chunkFilename:指定了非入口文件的名称static/js/name.chunk.js。

publicPath:指定服务器读取时的路径,此处设置为。

devtoolModuleFilenameTemplate:这里是一个函数,指定了map位于磁盘的位置。

resolve

modules:指定了模块的搜索的位置,这里设置为node_modules。

extensions:指明在引用模块时哪些后缀名能够忽略,这里忽略的文件名包括.js/.jsx/.web.js/.web.jsx等。

alias:建立 import 或 require 的别名,使得部分模块的引用变得简单,安装上文的设置,如今咱们能够直接引用react-native和react-native-web了。

plugins:此处使用了ModuleScopePlugin的实例,用以限制本身编写的模块只能从src目录中引入。

modules

strictExportPresence:这里设置为true,代表文件中若是缺乏exports时会直接报错而不是警告。

rules:

Rule1:对js/jsx文件前置使用eslintFormatter,设置formatter格式为eslintFormatter。

Rule2:对exclude中的众多文件类型不使用file-loader,并设置其它文件打包后的名称按'static/media/name.[hash:8].ext'格式设置。

Rule3: 对js/jsx文件调用babel-loader处理转换。

Rule4: 对css文件,按顺序调用style-loader,css-loader,postcss-loader进行处理。

plugins

这里的一些插件,有的可能咱们还比较陌生,咱们一一介绍。

InterpolateHtmlPlugin:和HtmlWebpackPlugin串行使用,容许在index.html中添加变量。

HtmlWebpackPlugin:自动生成带有入口文件引用的index.html。

NamedModulesPlugin:当开启 HMR 的时候使用该插件会显示模块的相对路径,建议用于开发环境。

DefinePlugin:这里咱们设置了process.env.NODE_ENV的值为development。

HotModuleReplacementPlugin:启用模块热替换。

CaseSensitivePathsPlugin:若是路径有误则直接报错。

WatchMissingNodeModulesPlugin:此插件容许你安装库后自动从新构建打包文件。

new webpack.IgnorePlugin(/^.\/locale$/, /moment$/):忽略所匹配的moment.js。

node

设置node的dgram/fs/let/tls模块的的值,若是在其它环境中使用时值为empty。

performance

hints: false:不提示测试环境的打包结果。

上文一直讨论的是,webpack各设置项的基本意义,目的在于让你在有相关需求时,能知道该从哪一项下手查询。不过看到这里,若是你以前从未上手操做过webpack可能依旧不知道该如何使用,下面我分析一下,我在本身的项目中是如何使用的。

一些工程实践建议

官方文档的guides部分已经就如何实践提出了较多的建议,建议阅读如下内容前先行阅读。

结合npm使用

webpack在安装后有多种调用方法。

在命令行中直接传入参数使用(这个实际我用的比较少)。

自定义 webpack.config.js文件,在其中完成配置,而后在命令行中执行webpack --config webpack.config.js来使用,配置文件能够是任何其它名称(若是是webpack.config.js,咱们直接使用webpack命令)。

结合npm使用,在package.json文件中的scripts对象中添加相关命令使用,以后经过npm run使用,以下:

"scripts": {
"build:prod": "webpack --progress --colors --watch --config webpack.prod.js",
"build:dev": "webpack --progress --colors --watch --config webpack.dev.js"
}
上面咱们分别构建了webpack.prod.js和webpack.dev.js来分别生成开发环境和生产环境的代码,在命令行中执行npm run build:prod和npm run build:dev便可生成对应代码。

为生产环境指定合理的缓存

关于缓存,官方文档中有一节讲解的很是详细,请参见缓存。

合理分割代码

webpack提供了三种分割代码的方法,分别是经过entry,经过CommonsChunkPlugin插件和经过动态import(在webpack1.x中时也经常使用require.ensure来依据路由分割代码)。

entry的配置经常使用于多页应用,CommonsChunkPlugin的使用前文已作简要叙述,下面简单叙述下代码分割原则及我实际工做中是如何使用动态import来分割代码的。

分割原则

目前工做中主要依据两个原则来分隔代码:

前端路由:依据路由对应的页面进行分割,这种分割以后的体验相似于小程序中每次打开新页加载对应页面的js文件。

针对逻辑交互比较复杂的页面,若是某个较复杂的组件需被某操做触发后才呈现,也会把该组件分割出来。

分割方法

咱们知道动态import返回值实际上是一个Promise,基于此,对应于我用的React,我常采用如下函数辅助加载。

// lib.js 定义懒加载函数
module.exports.withLazyLoading = function withLazyLoading(getComponent,Spinner = null) {
return class LazyLoadingWrapper extends React.Component {
constructor(props) {
super(props);
this.state = ({
Component: null,
})
}

componentWillMount() {
        const {onLoadingStart, onLoadingEnd, onError} = this.props;
          onLoadingStart();
        getComponent()
            .then(esModule => {
                this.setState({Component: esModule.default})
            })
            .catch(err => {
                onError(err, this.props)
            })
    }

    render() {
        const {Component} = this.state;
        if (!Component) return Spinner;
        return <Component {https://user-gold-cdn.xitu.io/2017/11/23/15fe79d230c8387dthis.props} />
    }
}复制代码

};
对代码的分割方法以下:

// 在须要的地方调用懒加载函数
import {withLazyLoading} from "lib";
//
import {Loading} from 'Loadings';

export default withLazyLoading(
() => {
return import (/ webpackChunkName: "ConCard" / "../../containers/ConCard.js")
}, Loading());
简要的说明一下上述代码的意义,懒加载函数withLazyLoading接受动态import的组件和一个加载动画做为参数,动态import的组件加载成功前显示加载动画组件,成功后显示import的组件,经过自定义各类各样的Spinner加载动画,咱们能够实现优雅的js文件加载过程。

观察打包后文件的结构,合理进行优化

使用webpack --json > stats.json命令能够生成一个包含依赖关系的json文件。webpack提供了多种可视化工具帮咱们分析这个文件,我最喜欢的工具插件是BundleAnalyzerPlugin,可经过下述方法引入该插件:

new BundleAnalyzerPlugin({
analyzerMode: 'static'
})
添加此插件,再次构建完成时,浏览器中将自动打开一个相似下面这样的网页:

BundleAnalyzerPlugin

这样咱们能够轻易分析咱们的代码分割是否合理,好比:

分割后文件过大的主要缘由是在于引入了那些模块。

分析大多后的多文件中存不存在对某些比较大的模块的重复引用,方便咱们进一步修正本身的配置文件。

上图是我以前项目中的一张截图,第一次见到这张图时仍是给了我不少后期优化的思路的,引用chat.js的同时引入了moment.js,而实际上该页面只有一张图表,这让我考虑另寻图表解决方案,lodash,velocity在最初的项目中使用过,后逐步去除,属于遗留代码,如今还存在说明在局部可能仍是用到了,这都是以后编码的改进方向。

后记

总以为技术类的文章也是该有生命力的,花了很久写完本文,回头看发现有的内容仍是没有表达或交待清楚。因此有任何建议,请随意提出,咱们在Chat中继续讨论,我也将对本文作长期持续的修改。

针对webpack3.5.5官网文档,使用mindNode制做了一个思惟导图的草稿,此思惟导图还需完善,以后将持续修改,点击此处可查看,该思惟导图示例以下。

思惟导图局部示例

另外,关于webpack1和webapck2的区别,官方文档中有一部分作了详细的讲解,因此本文中不作赘述,看完之后若是还有疑问,以后咱们再详细讨论。

相关文章
相关标签/搜索