如何解决webpack 内存溢出使用时内存占用过大的问题

1495被浏览157720分享邀请回答33225 条评论分享收藏感谢收起10031 条评论分享收藏感谢收起查看更多回答2016年, Vue框架在社区中逐渐活跃了起来, 同时公司也有更多的产品线启动, 这时很多团队需要做运营类管理后台, 而此时前端人力稀缺. 为了不重复建设和解放前端人力,
团队就准备基于webpack+vue框架做一个针对运营后台的前端打包和工程的解决方案, 然后进行公司内推广和培训, 让后端同学参与进来. 前端同学负责工程的建设和组件的开发, 后端同学负责具体业务开发.
到目前为止, 有5个以上团队采用这套前端解决方案进行运营后台或者工具项目开发, 极大的释放了前端人力, 效果不错. 在不断的演进中, 整个前端的客户的技术栈也逐渐由scrat+zepto 转型webpack+vue的开发方式.
因为是针对运营后台的设计初衷, 对性能和seo没有要求以及其他语言(java)的支持,采用是vue前端渲染方式进行设计的. 但随着新业务的发展, 加上vue支持服务端渲染的特性, 我们想在服务端渲染技术做一下研究和实践.
如下就针对webpack+vue服务端渲染进行相关研究和实践. 目前公司所有的前端新项目都是采用egg框架进行开发, 接下来主要讲的是基于egg项目如何实现webpack+vue工程化构建和服务端客户端渲染技术落地.
我们要解决什么问题
针对背景里面提到的一些问题, 基于webpack + egg项目的工程化, 当初想到和后面实践中遇到问题, 主要有如下问题需要解决:
Vue服务端渲染性能如何?
webpack 客户端(browser)运行模式打包支持
webpack 服务端(node)运行模式打包支持
如何实现服务端和客户端代码修改webpack热更新功能
webpack打包配置太复杂(客户端,服务端), 如何简化和多项目复用
开发, 测试, 正式等多环境支持, css/js/image的压缩和hash, cdn等功能如何配置, 页面依赖的css和js如何加载
如何快速扩展出基于vue, react前端框架服务端和客户端渲染的解决方案
webpack工程设计
我们知道webpack是一个前端打包构建工具, 功能强大, 意味的配置也会复杂. 我们可以通过针对vue, react等前端框架,采用不同的配置构建不同的解决方案.
虽然这样能实现, 但持续维护的成本大, 多项目使用时就只能采用拷贝的方式, 另外还有一些优化和打包技巧都需要各自处理.
基于以上的一些问题和想法, 我希望基于webpack的前端工程方案大概是这个样子:
webpack太复杂, 项目可重复性和维护性低, 是不是可以把基础的配置固化, 然后基于基础的配置扩展出具体的解决方案(vue/react等打包方案).
webpack配置支持多环境配置, 根据环境很方便的设置是否开启source-map, hash, 压缩等特性.
webpack配置的普通做法是写配置, 是不是可以采用面向对象的方式来编写配置.
能够基于基础配置很简单的扩展出基于vue, react 服务端渲染的解决方案
针对egg + webpack内存编译和热更新功能与框架无关, 可以抽离出来, 做成通用的插件
webpack基础配置固化
在使用webpack对不同的前端框架进行打包和处理时, 有些配置是公共的, 有些特性是共性的, 我们把这些抽离出来, 并提供接口进行设置和扩展.
option: entry读取, output, extensions 等基础配置
loader: babel-loader, json-loader, url-loader, style-loader, css-loader, sass-loader, less-loader, postcss-loader, autoprefixer 等
plugin: webpack.DefinePlugin(process.env.NODE_ENV), CommonsChunkPlugin等
js/css/image 是否hash
js/css/image 是否压缩
js/css commonChunk处理
开发辅助特性
编译进度条插件 ProgressBarPlugin
资源依赖表
ManifestPlugin
热更新处理
HotModuleReplacementPlugin
以上一些公共特性是初步梳理出来的, 不与具体的前端框架耦合. 针对这些特性可以单独写成一个npm组件, 并提供扩展接口进行覆盖, 删除和扩展功能.
在具体实现时, 可以根据环境变量 process.env.NODE_ENV 默认开启或者关闭一些特性. 比如本地开发时, 关闭 js/css/image 的hash和压缩,
开启热更新功能.
webpack配置面向对象实现
针对上面梳理的公共基础配置, 可以把webpack配置分离成三部分: option, loader, plugin
针对客户端和服务端打包的差异性, 设计成三个类 WebpackBaseBuilder, WebpackClientBuilder, WebpackServerBuilder
基于以上的想法, 大概代码实现:
WebpackBaseBuilder.js 公共配置
class WebpackBaseBuilder {
constructor(config) {
this.config = config;
this.initConfig();
this.initOption();
this.initConfigLoader();
this.initConfigPlugin();
initConfig() {
this.prod = process.env.NODE_ENV === 'production';
this.options = {};
this.loaders = [];
this.plugins = [];
this.setUglifyJs(this.prod);
this.setFileNameHash(this.prod);
this.setImageHash(this.prod);
this.setCssHash(this.prod);
this.setCssExtract(false);
initOption() {
this.options = {
entry: Utils.getEntry(this.config.build.entry),
resolve: {
extensions: ['.js']
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
initConfigLoader() {
// default custom loader config list, call createWebpackLoader to webpack loader
this.configLoaders = [
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: require.resolve('url-loader'),
limit: 1024
dynamic: () =& {
name: this.imageName
initConfigPlugin() {
// default custom plugin config list, call createWebpackPlugin to webpack plugin
this.configPlugins = [
enable: () =& {
return this.isUglifyJS;
clazz: webpack.optimize.UglifyJsPlugin,
args: () =& {
compress: {
warnings: false,
dead_code: true,
drop_console: true,
drop_debugger: true
setOption(option) {
this.options = merge(this.options, option);
setPublicPath(publicPath) {
this.options = merge(this.options, { output: { publicPath } });
setDevTool(devtool, force) {
if (!this.prod || force) {
this.options = merge(this.options, { devtool });
setConfigLoader(loader, isHead) {
setConfigPlugin(plugin, isHead) {
setLoader(loader, isHead) {
setPlugin(plugin, isHead) {
createWebpackLoader() {
return this.loaders;
createWebpackPlugin() {
return this.plugins;
create() {
this.createWebpackLoader();
this.createWebpackPlugin();
return this.getWebpackConfig();
getWebpackConfig() {
return merge({
rules: this.loaders
plugins: this.plugins
this.options);
setMiniCss(isMiniCss) {
this.isMiniCss = isMiniCss;
setUglifyJs(isUglifyJS) {
this.isUglifyJS = isUglifyJS
setFileNameHash(isHash, len = 7) {
if (isHash) {
this.filename = Utils.assetsPath(this.config, `js/[name].[hash:${len}].js`);
this.chunkFilename = Utils.assetsPath(this.config, `js/[id].[chunkhash:${len}].js`);
this.filename = Utils.assetsPath(this.config, 'js/[name].js');
this.chunkFilename = Utils.assetsPath(this.config, 'js/[id].js');
setImageHash(isHash, len = 7) {
if (isHash) {
this.imageName = Utils.assetsPath(this.config, `img/[name].[hash:${len}].[ext]`);
this.imageName = Utils.assetsPath(this.config, `img/[name].[ext]`);
setCssHash(isHash, len = 7) {
if (isHash) {
this.cssName = Utils.assetsPath(this.config, `css/[name].[contenthash:${len}].css`);
this.cssName = Utils.assetsPath(this.config, `img/[name].css`);
setCssExtract(isExtract) {
this.config.extractCss = isExtract;
module.exports = WebpackBaseBuilder;
WebpackServerBuilder.js 客户端打包公共配置
class WebpackClientBuilder extends WebpackBaseBuilder {
constructor(config) {
super(config);
this.initClientOption();
this.initClientConfigPlugin();
this.initHotEntry();
this.setCssExtract(this.prod);
this.setMiniCss(this.prod);
initHotEntry() {
initClientOption() {
initClientConfigPlugin() {
this.configPlugins.push({
enable: () =& {
return !this.prod;
clazz: webpack.HotModuleReplacementPlugin
module.exports = WebpackClientBuilder;
WebpackClientBuilder.js 服务端打包公共配置
class WebpackServerBuilder extends WebpackBaseBuilder {
constructor(config) {
super(config);
this.initServerOption();
this.setCssExtract(false);
initServerOption() {
this.setOption({
target: 'node',
libraryTarget: 'commonjs2',
path: path.join(this.config.baseDir, 'app/view')
externals: Utils.loadNodeModules()
module.exports = WebpackServerBuilder;
以上是正对webpack公共基础配置进行初步的设计, 还需不断完善, 目前已经基于此设想开发了
针对具体前端框架vue的打包方案实现
上面已经采用面向对象的方式实现了webpack的基础配置插件, 接下来我们在easywebpack的基础上扩展出
webpack+vue前端构建解决方案.
vue构建里面与vue相关主要有vue-style-loader和vue-html-loader, extensions,
process.env.VUE_ENV 环境变量, 我们在easywebpack上面扩展此特性即可.
Vue客户端和服务端打包公共配置config.js
const EasyWebpack = require('easywebpack');
const webpack = EasyWebpack.webpack;
const merge = EasyWebpack.merge;
exports.getLoader = config =& {
test: /\.vue$/,
loader: require.resolve('vue-loader'),
dynamic: () =& {
options: EasyWebpack.Loader.getStyleLoaderOption(config)
test: /\.html$/,
loader: require.resolve('vue-html-loader')
exports.initBase = options =& {
return merge({
resolve: {
extensions: ['.vue']
}, options);
exports.initClient = options =& {
return merge(exports.initBase(options), {
resolve: {
vue: 'vue/mon.js'
}, options);
exports.initServer = options =& {
return merge(exports.initBase(options), {
resolve: {
vue: 'vue/dist/mon.js'
plugins: [
// 配置 vue 的环境变量,告诉 vue 是服务端渲染,就不会做耗性能的 dom-diff 操作了
new webpack.DefinePlugin({
'process.env.VUE_ENV': '"server"'
}, options);
这些基础配置不复杂, 没有对所有的属性提供单独的方法进行设置, 直接通过setOption方法统一设置.
Vue客户端打包配置client.js
'use strict';
const EasyWebpack = require('easywebpack');
const baseConfig = require('./config');
class WebpackClientBuilder extends EasyWebpack.WebpackClientBuilder {
constructor(config) {
super(config);
this.setOption(baseConfig.initClient());
this.setConfigLoader(baseConfig.getLoader(this.config), true);
create() {
return super.create();
module.exports = WebpackClientBuilder;
Vue服务端打包配置server.js
'use strict';
const EasyWebpack = require('easywebpack');
const baseConfig = require('./config');
class WebpackServerBuilder extends EasyWebpack.WebpackServerBuilder {
constructor(config) {
super(config);
this.setOption(baseConfig.initServer());
this.setConfigLoader(baseConfig.getLoader(this.config), true);
module.exports = WebpackServerBuilder;
命令行运行编译 build.js
编译入口脚本
'use strict';
const EasyWebpack = require('easywebpack');
const clientConfig = require('./client')(config);
const serverConfig = require('./server')(config);
EasyWebpack.build([clientConfig, serverConfig]);
命令行运行
NODE_ENV=development && node build.js
NODE_ENV=production && node build.js
目前已经基于此实现开发了
egg + webpack内存编译和热更新
webpack基础配置和vue打包插件已完成, 那如何在本地开发实现修改代码自动编译功能呢?
webpack提供了很好的内存编译和热更新机制, 极大提高了编译效率和开发效率.
如果你仅仅是进行webpack编译客户端运行文件, 可以很方便的做到热更新机制: 修改代码, 不需要手动刷新界面, UI马上会自动更新.
这种更新不会刷新页面, 而是webpack通过监听文件修改, 通过socket建立客户端的连接, 把修改的内容通过socket发给浏览器,通过动态执行js达到页面更新的效果.
如果是修改Node.js服务端端代码想要项目自动重启和webpack编译内存继续存在而不是重新编译, 就需要借助egg框架的agent机制.
egg已经内置了worker和agent通信机制以及自动重启功能.
实现egg项目服务端代码修改项目自动重启的功能可以使用插件.
worker和agent通信机制: https://eggjs.org/zh-cn/core/cluster-and-ipc.html
当egg自动重启时, agent是不会销毁的, 这样就可以让webpack实例继续保持, 我们需要解决的是如何从webpack编译内存里面编译的文件内容.
内容包括webpack编译的服务端渲染文件和客户端用到js, css, image等静态资源. 这些内容我们都可以通过worker发现消息给agent, 然后读取内容,
再通过agent发送消息给worker. js, css, image等静态资源也这样处理的话就需要读取文件流,然后根据文件类型返回给对应的响应内容.
这里采用一种更简单的方式, 直接在agent里面启动一个koa的webpack编译服务, 这样就可以通过http的方式访问js, css, image等静态资源.
首先在agent中启动koa服务
针对webpack server 编译模式, 在egg agent里面单独启动koa编译服务, 同时开启跨域功能. agent.js 代码如下:
const webpack = require('webpack');
const koa = require('koa');
const cors = require('kcors');
const app = koa();
app.use(cors());
const Constant = require('./constant');
const Utils = require('./utils');
module.exports = agent =& {
const config = agent.config.webpack;
const webpackConfig = config.webpackConfig;
const compiler = webpack(webpackConfig);
const devMiddleware = require('koa-webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
watchOptions: {
ignored: /node_modules/,
app.use(devMiddleware);
app.listen(config.port + 1, err =& {
if (!err) {
agent.logger.info(`start webpack build service: http://127.0.0.1:${config.port}`);
koa服务启动以后, 就可以通过 http://127.0.0.1:port 方式访问js, css, image等静态资源.
agent中监听worker发送的消息
在agent.js 增加如下监听的代码:
通过agent.messenger.on 监听app worker发送过来的消息
agent.messenger.on(Constant.EVENT_WEBPACK_READ_FILE_MEMORY, data =& {
const fileContent = Utils.readWebpackMemoryFile(compiler, data.filePath);
if (fileContent) {
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, {
fileContent,
// agent.logger.error(`webpack server memory file[${data.filePath}] not exist!`);
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, {
fileContent: '',
在worker view 渲染文件读取里面调用如下方法, 获取webpack的编译内容
通过app.messenger.sendToAgent 向agent发送消息
通过app.messenger.on 监听agent发送过来的消息
function readFile(filePath){
return new Promise(resolve =& {
this.app.messenger.sendToAgent(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, {
this.app.messenger.on(Constant.EVENT_WEBPACK_READ_FILE_MEMORY_CONTENT, data =& {
resolve(data.fileContent);
在app/extend/application.js 挂载webpack实例, 以便进行扩展.
const WEBPACK = Symbol('Application#webpack');
module.exports = {
get webpack() {
if (!this[WEBPACK]) {
this[WEBPACK] = new FileSystem(this);
return this[WEBPACK];
针对此方案实现基于了egg的开发webpack server编译插件
关于koa的实现热编译和重启, 可以参考
本地开发内存文件读取与线上运行文件读取分离实现
在egg-view插件开发规范中,我们会在ctx上面挂载render方法, render方法会根据文件名进行文件读取, 模板与数据编译, 从而实现模板的渲染.如下就是controller的调用方式:
exports.index = function* (ctx) {
yield ctx.render('index/index.js', Model.getPage(1, 10));
那我们如何处理从webpack内存读取还是从本地文件读取呢? 其中最关键的一步是根据文件名进行文件读取, 只要view插件设计时, 把文件读取的方法暴露出来,就可以实现本地开发webpack热更新内存存储读取.
vue view engine设计实现:
const Engine = require('../../lib/engine');
const VUE_ENGINE = Symbol('Application#vue');
module.exports = {
get vue() {
if (!this[VUE_ENGINE]) {
this[VUE_ENGINE] = new Engine(this);
return this[VUE_ENGINE];
class Engine {
constructor(app) {
this.app = app;
this.config = app.config.vue;
this.cache = LRU(this.config.cache);
this.fileLoader = new FileLoader(app, this.cache);
this.renderer = vueServerRenderer.createRenderer();
this.renderOptions = Object.assign({
cache: this.cache,
}, this.config.renderOptions);
createBundleRenderer(code, renderOptions) {
return vueServerRenderer.createBundleRenderer(code, Object.assign({}, this.renderOptions, renderOptions));
* readFile(name) {
return yield this.fileLoader.load(name);
render(code, data = {}, options = {}) {
return new Promise((resolve, reject) =& {
this.createBundleRenderer(code, options.renderOptions).renderToString(data, (err, html) =& {
if (err) {
reject(err);
resolve(html);
ctx.render 方法
class View {
constructor(ctx) {
this.app = ctx.app;
* render(name, locals, options = {}) {
// 我们通过覆写app.vue.readFile即可改变文件读取逻辑
const code = yield this.app.vue.readFile(name);
return this.app.vue.render(code, { state: locals }, options);
renderString(tpl, locals) {
return this.app.vue.renderString(tpl, locals);
module.exports = View;
服务器view渲染插件实现
通过webpack实例覆写app.vue.readFile 改变从webpack内存读取文件内容.
if (app.vue) {
app.vue.readFile = fileName =& {
const filePath = path.isAbsolute(fileName) ? fileName : path.join(app.config.view.root[0], fileName);
if (/\.js$/.test(fileName)) {
// webpack 实例是有[egg-webpack]插件挂载上去的.
return app.webpack.fileSystem.readServerFile(filePath, fileName);
return app.webpack.fileSystem.readClientFile(filePath, fileName);
app.messenger.on(app.webpack.Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, data =& {
if (data.state) {
const filepath = app.config.webpackvue.build.manifest;
const promise = app.webpack.fileSystem.readClientFile(filepath);
promise.then(content =& {
fs.writeFileSync(filepath, content, 'utf8');
webpack + vue 编译插件实现
说明: 在最开始的实现时,一直在摸索, 为了尽快满足项目, 整个功能是在一个插件中实现的.
随着业务稳定下来, 就对这边个插件进行了分离和重新实现, 才有了上面的三个插件, 目地是让webpack编写更简单, 扩展更容易.
egg+webpack+vue项目实战
用egg-init 初始化一个项目和安装依赖后, 我们添加 @hubcarl/egg-view-vue 和 egg-view-vue-ssr依赖, 添加egg-webpack 和 egg-webpack-vue 开发依赖
npm i @hubcarl/egg-view-vue --save
npm i @hubcarl/egg-view-vue --save
npm i egg-webpack --save-dev
npm i egg-webpack-vue --save-dev
config/plugin.js
exports.vue = {
enable: true,
package: '@hubcarl/egg-view-vue'
exports.vuessr = {
enable: true,
package: 'egg-view-vue-ssr'
exports.webpack = {
enable: true,
package: 'egg-webpack'
exports.webpackvue = {
enable: true,
package: 'egg-webpack-vue'
config.default.js
exports.view = {
cache: false
exports.static = {
router: '/public', // 请求进来的前缀,避免和应用的 router 冲突,默认是 `/public`
// maxAge: 3600 * 24 * 180, // maxAge 缓存,默认 1 年
dir: path.join(app.baseDir, 'public') // 静态文件目录,默认是 `${baseDir}/app/pulbic`
config.local.js
const webpackConfig = {
baseDir: path.resolve(__dirname, '../'),
port: 8090,
path: 'public',
publicPath: '/public/',
prefix: 'static',
entry: [path.join(baseDir, 'app/web/page')],
commonsChunk: ['vendor']
webpack: {
styleLoader: 'vue-style-loader',
loaderOption: {
includePaths: [path.join(baseDir, 'app/web/asset/style')]
exports.webpack = {
port: webpackConfig.webpackvue.build.port,
clientConfig: require(path.join(app.baseDir, 'build/client')),
serverConfig: require(path.join(app.baseDir, 'build/server'))
exports.webpackvue = webpackConfig.webpackvue;
package.json 增加如下命令
"scripts": {
"build-dev": "NODE_ENV=development node build",
"build-prod": "NODE_ENV=production node build",
"start": "WORKERS=1 NODE_ENV=development node index.js",
"start-prod": "WORKERS=1 && NODE_ENV=production node index.js",
编写webpack配置
在项目根目录下增加build文件夹, 然后分别编写client和server webpack配置文件
client/dev.js
const ClientBaseBuilder = require('./base');
class ClientDevBuilder extends ClientBaseBuilder {
constructor() {
this.setEggWebpackPublicPath();
this.setDevTool('eval-source-map');
this.setCssExtract(false);
module.exports = new ClientDevBuilder().create();
sever/dev.js
const ServerBaseBuilder = require('./base');
class ServerDevBuilder extends ServerBaseBuilder {
constructor() {
this.setEggWebpackPublicPath();
module.exports = new ServerDevBuilder().create();
命令行编译文件到磁盘
增加 build.js 文件
const EggWebpackVue = require('egg-webpack-vue');
const clientConfig = require('./client');
const serverConfig = require('./server');
EggWebpackVue.EasyWebpack.build([clientConfig, serverConfig]);
npm run build-dev
npm run build-prod
client 文件默认编译到public目录下,
server 编辑结果默认到 app/view` 目录下
npm start 会自动通过egg-webpack 启动编译流程, 无需单独运行npm run build-dev 或 npm run build-prod. 启动流程效果如下:
访问: http://127.0.0.1:7001
基于Vue多页面和单页面服务端渲染同构工程骨架项目
基于vue + axios 多页面服务器客户端同构入口: http://127.0.0.1:7001
基于vue + vuex + vue-router + axios 单页面服务器客户端同构入口: http://127.0.0.1:7001/app
关于性能和优化以及问题 (单独总结)
egg+webpack+vue 依赖
egg view plugin for vue
vue server side render solution for egg-view-vue
webpack dev server plugin for egg, support read file in memory and hot reload
egg webpack building solution for vue
programming instead of configuration, webpack is no longer complex彻底解决Webpack打包性能问题a year ago运行webpack test.js
在我的2015款RMBP13,i5处理器,全SSD下,性能是这样的:没错你没有看错,这几个第三方轮子加起来有整整668个模块,全部打包需要20多秒。这意味着什么呢?你每次对业务代码的修改,gulp 或者 Webpack 监测到后都会重新打包,你要足足等20秒才能看到自己的修改结果。但是需要重新打包的只有你的业务代码,这些第三方库是完全不用重新打包的,它们的存在只会拖累打包性能。所以我们要找一些方法来优化这个过程。配置externalsWebpack 可以配置 externals 来将依赖的库指向全局变量,从而不再打包这个库,比如对于这样一个文件:import React from 'react';
console.log(React);
如果你在 Webpack.config.js 中配置了externals:module.exports = {
externals: {
'react': 'window.React'
//其它配置忽略......
等于让 Webpack 知道,对于 react 这个模块就不要打包啦,直接指向 window.React 就好。不过别忘了加载 react.min.js,让全局中有 React 这个变量。我们来看看性能,因为不用打包 React 了所以速度自然很快,包也很小:配置externals的缺陷问题如果就这么简单地解决了的话,那我就没必要写这篇文章了,下面我们加一个 react 的动画库 react-addons-css-transition-group 来试一试:import React from 'react';
import ReactAddonsCssTransitionGroup from 'react-addons-css-transition-group';
console.log(React);
对,你没有看错,我也没有截错图,新加了一个很小很小的动画库之后,性能又爆炸了。从模块数来看,一定是 Webpack 又把 react 重新打包了一遍。我们来看一下为什么一个很小很小的动画库会导致 Webpack 又傻傻地把 react 重新打包了一遍。找到 react-addons-css-transition-group 这个模块,然后看看它是怎么写的:// react-addons-css-transition-group模块
// 入口文件 index.js
module.exports = require('react/lib/ReactCSSTransitionGroup');
这个动画模块就只有一行代码,唯一的作用就是指向 react 下面的一个子模块,我们再来看看这个子模块是怎么写的:// react模块
// react/lib/ReactCSSTransitionGroup.js
var React = require('./React');
var ReactTransitionGroup = require('./ReactTransitionGroup');
var ReactCSSTransitionGroupChild = require('./ReactCSSTransitionGroupChild');
//....剩余代码忽略
这个子模块又反回去依赖了 react 整个库的入口,这就是拖累 Webpack 的罪魁祸首。总而言之,问题是这样产生的:Webpack 发现我们依赖了 react-addons-css-transition-group; Webpack 去打包 react-addons-css-transition-group 的时候发现它依赖了 react 模块下的一个叫 ReactTransitionGroup.js 的文件,于是 Webpack 去打包这个文件;ReactTransitionGroup.js 依赖了整个 react 的入口文件 React.js,虽然我们设置了 externals ,但是 Webpack 不知道这个入口文件等效于 react 模块本身,于是我们可爱又敬业的 Webpack 就把整个 react 又重新打包了一遍。读到这里你可能会有疑问,为什么不能把这个动画库也设置到 externals 里,这样不是就不用打包了吗?问题就在于,这个动画库并没有提供生产环境的文件,或者说这个库根本没有提供 react-addons-css-transition-group.min.js 这个文件。这个问题不只存在于 react-addons-css-transition-group 中,对于 react 的大多数现有库来说都有这个依赖关系复杂的问题。初级解决方法所以对于这个问题的解决方法就是,手工打包这些 module,然后设置 externals ,让 Webpack 不再打包它们。我们需要这样一个 lib-bundle.js 文件:window.__LIB["react"] = require("react");
window.__LIB["react-addons-css-transition-group"] = require("react-addons-css-transition-group");
// ...其它依赖包
我们在这里把一些第三方库注册到了 window.__LIB 下,这些库可以作为底层的基础库,免于重复打包。然后执行 webpack lib-bundle.js lib.js
得到打包好的 lib.js。然后去设置我们的 externals :var webpack = require('webpack');
module.exports = {
externals: {
'react': 'window.__LIB["react"]',
'react-addons-css-transition-group': 'window.__LIB["react-addons-css-transition-group"]',
//其它配置忽略......
这时由于 externals 的存在,Webpack 打包的时候就会避开这些模块超多,依赖关系复杂的库,把这些第三方 module 的入口指向预先打包好的 lib.js 的入口 window.__LIB,从而只打包我们的业务代码。终极解决方法上面我们提到的方法本质上就是一种“动态链接库(dll)”的思想,这在 windows 系统下面是一种很常见的思想。一个 dll 包,就是一个很纯净的依赖库,它本身不能运行,是用来给你的 app 或者业务代码引用的。同样的 Webpack 最近也新加入了这个功能:webpack.DllPlugin。使用这个功能需要把打包过程分成两步: 打包ddl包 引用ddl包,打包业务代码首先我们来打包ddl包,首先配置一个这样的 ddl.config.js:const webpack = require('webpack');
const vendors = [
'react-dom',
'react-router',
// ...其它库
module.exports = {
path: 'build',
filename: '[name].js',
library: '[name]',
"lib": vendors,
plugins: [
new webpack.DllPlugin({
path: 'manifest.json',
name: '[name]',
context: __dirname,
webpack.DllPlugin 的选项中:path 是 manifest.json 文件的输出路径,这个文件会用于后续的业务代码打包;name 是 dll 暴露的对象名,要跟 output.library 保持一致;context 是解析包路径的上下文,这个要跟接下来配置的 webpack.config.js 一致。运行Webpack,会输出两个文件一个是打包好的 lib.js,一个就是 manifest.json,它里面的内容大概是这样的:{
"name": "vendor_ac51ba426d4f259b8b18",
"content": {
"./node_modules/react/react.js": 1,
"./node_modules/react/lib/React.js": 2,
"./node_modules/react/node_modules/object-assign/index.js": 3,
"./node_modules/react/lib/ReactChildren.js": 4,
"./node_modules/react/lib/PooledClass.js": 5,
"./node_modules/react/lib/reactProdInvariant.js": 6,
// ............
接下来我们就可以快乐地打包业务代码啦,首先写好打包配置文件 webpack.config.js:const webpack = require('webpack');
module.exports = {
path: 'build',
filename: '[name].js',
app: './src/index.js',
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./manifest.json'),
webpack.DllReferencePlugin 的选项中:context 需要跟之前保持一致,这个用来指导 Webpack 匹配 manifest.json 中库的路径;manifest 用来引入刚才输出的 manifest.json 文件。DllPlugin 本质上的做法和我们手动分离这些第三方库是一样的,但是对于包极多的应用来说,自动化明显加快了生产效率。赞赏还没有人赞赏,快来当第一个赞赏的人吧!340收藏分享举报文章被以下专栏收录简介 is not defined推荐阅读{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&isPending&:false,&contributes&:[{&sourceColumn&:{&lastUpdated&:,&description&:&&,&permission&:&COLUMN_PUBLIC&,&memberId&:1360792,&contributePermission&:&COLUMN_PUBLIC&,&translatedCommentPermission&:&all&,&canManage&:true,&intro&:&简介 is not defined&,&urlToken&:&starkwang&,&id&:11622,&imagePath&:&faead8d536.jpeg&,&slug&:&starkwang&,&applyReason&:&0&,&name&:&一只码农的技术日记&,&title&:&一只码农的技术日记&,&url&:&https:\u002F\\u002Fstarkwang&,&commentPermission&:&COLUMN_ALL_CAN_COMMENT&,&canPost&:true,&created&:,&state&:&COLUMN_NORMAL&,&followers&:4958,&avatar&:{&id&:&faead8d536&,&template&:&https:\u002F\\u002F{id}_{size}.jpeg&},&activateAuthorRequested&:false,&following&:false,&imageUrl&:&https:\u002F\\u002Ffaead8d536_l.jpeg&,&articlesCount&:26},&state&:&accepted&,&targetPost&:{&titleImage&:&https:\u002F\\u002F897c2df2f21aa6b2f9f4a_r.png&,&lastUpdated&:,&imagePath&:&897c2df2f21aa6b2f9f4a.png&,&permission&:&ARTICLE_PUBLIC&,&topics&:[,225],&summary&:&这几天写腾讯实习生 Mini 项目的时候用上了 react 全家桶,当然同时引入了 Webpack 作为打包工具。但是开发过程中遇到一个很棘手的问题就是,react 加上 react-router、material-ui、superagent、eventproxy 这些第三方轮子一共有好几百个 module,Webpack …&,&copyPermission&:&ARTICLE_COPYABLE&,&translatedCommentPermission&:&all&,&likes&:0,&origAuthorId&:0,&publishedTime&:&T14:51:58+08:00&,&sourceUrl&:&&,&urlToken&:,&id&:883604,&withContent&:false,&slug&:,&bigTitleImage&:false,&title&:&彻底解决Webpack打包性能问题&,&url&:&\u002Fp\u002F&,&commentPermission&:&ARTICLE_ALL_CAN_COMMENT&,&snapshotUrl&:&&,&created&:,&comments&:0,&columnId&:11622,&content&:&&,&parentId&:0,&state&:&ARTICLE_PUBLISHED&,&imageUrl&:&https:\u002F\\u002F897c2df2f21aa6b2f9f4a_r.png&,&author&:{&bio&:&企鹅培育与饲养&,&isFollowing&:false,&hash&:&d965f32ae58ad3a48a1585a4&,&uid&:68,&isOrg&:false,&slug&:&starkwei&,&isFollowed&:false,&description&:&\u002Fstarkwang&,&name&:&Stark伟&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fstarkwei&,&avatar&:{&id&:&v2-cfafbe3e362ecf37f1d5fcb7f488f13c&,&template&:&https:\u002F\\u002F50\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},&memberId&:1360792,&excerptTitle&:&&,&voteType&:&ARTICLE_VOTE_CLEAR&},&id&:402137}],&title&:&彻底解决Webpack打包性能问题&,&author&:&starkwei&,&content&:&\u003Cp\u003E这几天写腾讯实习生 Mini 项目的时候用上了 react 全家桶,当然同时引入了 Webpack 作为打包工具。但是开发过程中遇到一个很棘手的问题就是,react 加上 react-router、material-ui、superagent、eventproxy 这些第三方轮子一共有好几百个 module,Webpack 的打包速度极慢。这对于开发是非常不好的体验,同时效率也极低。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E问题分析\u003C\u002Fh2\u003E\u003Cp\u003E我们先来看一下完全没有任何优化的时候,Webpack 的打包速度(使用了jsx和babel的loader)。下面是我们的测试文件:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&c1\&\u003E\u002F\u002Ftest.js\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Ereact\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'react'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003EReactAddonsCssTransitionGroup\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'react-addons-css-transition-group'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003EreactDOM\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'react-dom'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003EreactRouter\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'react-router'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Esuperagent\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s2\&\u003E\&superagent\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Eeventproxy\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s2\&\u003E\&eventproxy\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E运行\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Ewebpack test.js\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E在我的2015款RMBP13,i5处理器,全SSD下,性能是这样的:\u003C\u002Fp\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fd5aacfff08af1dca999e_b.png\& data-rawwidth=\&780\& data-rawheight=\&124\& class=\&origin_image zh-lightbox-thumb\& width=\&780\& data-original=\&https:\u002F\\u002Fd5aacfff08af1dca999e_r.png\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='780'%20height='124'&&\u002Fsvg&\& data-rawwidth=\&780\& data-rawheight=\&124\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&780\& data-original=\&https:\u002F\\u002Fd5aacfff08af1dca999e_r.png\& data-actualsrc=\&https:\u002F\\u002Fd5aacfff08af1dca999e_b.png\&\u003E\u003Cp\u003E没错你没有看错,这几个第三方轮子加起来有整整668个模块,全部打包需要20多秒。\u003C\u002Fp\u003E\u003Cp\u003E这意味着什么呢?你每次对业务代码的修改,gulp 或者 Webpack 监测到后都会重新打包,你要足足等20秒才能看到自己的修改结果。\u003C\u002Fp\u003E\u003Cp\u003E但是需要重新打包的只有你的业务代码,这些第三方库是完全不用重新打包的,它们的存在只会拖累打包性能。所以我们要找一些方法来优化这个过程。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E配置externals\u003C\u002Fh2\u003E\u003Cp\u003EWebpack 可以配置 externals 来将依赖的库指向全局变量,从而不再打包这个库,比如对于这样一个文件:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kr\&\u003Eimport\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003EReact\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Efrom\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'react'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&nx\&\u003Econsole\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003Elog\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003EReact\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E如果你在 Webpack.config.js 中配置了externals:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003Emodule\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003Eexports\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Eexternals\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s1\&\u003E'react'\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'window.React'\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F其它配置忽略...... \u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E};\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E等于让 Webpack 知道,对于 react 这个模块就不要打包啦,直接指向 window.React 就好。不过别忘了加载 react.min.js,让全局中有 React 这个变量。\u003C\u002Fp\u003E\u003Cp\u003E我们来看看性能,因为不用打包 React 了所以速度自然很快,包也很小:\u003C\u002Fp\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002F5d26d23ef6e3ed2a9c04e0_b.png\& data-rawwidth=\&740\& data-rawheight=\&130\& class=\&origin_image zh-lightbox-thumb\& width=\&740\& data-original=\&https:\u002F\\u002F5d26d23ef6e3ed2a9c04e0_r.png\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='740'%20height='130'&&\u002Fsvg&\& data-rawwidth=\&740\& data-rawheight=\&130\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&740\& data-original=\&https:\u002F\\u002F5d26d23ef6e3ed2a9c04e0_r.png\& data-actualsrc=\&https:\u002F\\u002F5d26d23ef6e3ed2a9c04e0_b.png\&\u003E\u003Cbr\u003E\u003Ch2\u003E配置externals的缺陷\u003C\u002Fh2\u003E\u003Cp\u003E问题如果就这么简单地解决了的话,那我就没必要写这篇文章了,下面我们加一个 react 的动画库 \u003Cb\u003Ereact-addons-css-transition-group\u003C\u002Fb\u003E 来试一试:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kr\&\u003Eimport\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003EReact\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Efrom\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'react'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&kr\&\u003Eimport\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003EReactAddonsCssTransitionGroup\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Efrom\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'react-addons-css-transition-group'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&nx\&\u003Econsole\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003Elog\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003EReact\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002F304ea169f738e890a4e87d_b.png\& data-rawwidth=\&744\& data-rawheight=\&122\& class=\&origin_image zh-lightbox-thumb\& width=\&744\& data-original=\&https:\u002F\\u002F304ea169f738e890a4e87d_r.png\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='744'%20height='122'&&\u002Fsvg&\& data-rawwidth=\&744\& data-rawheight=\&122\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&744\& data-original=\&https:\u002F\\u002F304ea169f738e890a4e87d_r.png\& data-actualsrc=\&https:\u002F\\u002F304ea169f738e890a4e87d_b.png\&\u003E\u003Cp\u003E对,你没有看错,我也没有截错图,新加了一个很小很小的动画库之后,性能又爆炸了。从模块数来看,一定是 Webpack 又把 react 重新打包了一遍。\u003C\u002Fp\u003E\u003Cp\u003E我们来看一下为什么一个很小很小的动画库会导致 Webpack 又傻傻地把 react 重新打包了一遍。找到\u003Cb\u003E react-addons-css-transition-group\u003C\u002Fb\u003E 这个模块,然后看看它是怎么写的:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&c1\&\u003E\u002F\u002F react-addons-css-transition-group模块\u003C\u002Fspan\u003E\n\u003Cspan class=\&c1\&\u003E\u002F\u002F 入口文件 index.js\u003C\u002Fspan\u003E\n\u003Cspan class=\&nx\&\u003Emodule\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003Eexports\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'react\u002Flib\u002FReactCSSTransitionGroup'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E这个动画模块就只有一行代码,唯一的作用就是指向 react 下面的一个子模块,我们再来看看这个子模块是怎么写的:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&c1\&\u003E\u002F\u002F react模块\u003C\u002Fspan\u003E\n\u003Cspan class=\&c1\&\u003E\u002F\u002F react\u002Flib\u002FReactCSSTransitionGroup.js\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003EReact\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'.\u002FReact'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003EReactTransitionGroup\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'.\u002FReactTransitionGroup'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003EReactCSSTransitionGroupChild\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'.\u002FReactCSSTransitionGroupChild'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&c1\&\u003E\u002F\u002F....剩余代码忽略\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E这个子模块又反回去依赖了 react 整个库的入口,这就是拖累 Webpack 的罪魁祸首。\u003C\u002Fp\u003E\u003Cp\u003E总而言之,问题是这样产生的:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003EWebpack 发现我们依赖了\u003Cb\u003E react-addons-css-transition-group\u003C\u002Fb\u003E;\u003C\u002Fli\u003E\u003Cli\u003E Webpack 去打包\u003Cb\u003E react-addons-css-transition-group\u003C\u002Fb\u003E 的时候发现它依赖了 react 模块下的一个叫 ReactTransitionGroup.js 的文件,于是 Webpack 去打包这个文件;\u003C\u002Fli\u003E\u003Cli\u003EReactTransitionGroup.js 依赖了整个 react 的入口文件 React.js,\u003Cb\u003E虽然我们设置了 externals ,但是 Webpack 不知道这个入口文件等效于 react 模块本身\u003C\u002Fb\u003E,于是我们可爱又敬业的 Webpack 就把整个 react 又重新打包了一遍。\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E读到这里你可能会有疑问,为什么不能把这个动画库也设置到 externals 里,这样不是就不用打包了吗?\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E问题就在于,这个动画库并没有提供生产环境的文件,或者说这个库根本没有提供 react-addons-css-transition-group.min.js 这个文件。\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E这个问题不只存在于\u003Cb\u003E react-addons-css-transition-group\u003C\u002Fb\u003E 中,对于 react 的大多数现有库来说都有这个依赖关系复杂的问题。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E初级解决方法\u003C\u002Fh2\u003E\u003Cp\u003E所以对于这个问题的解决方法就是,手工打包这些 module,然后设置 externals ,让 Webpack 不再打包它们。\u003C\u002Fp\u003E\u003Cp\u003E我们需要这样一个 lib-bundle.js 文件:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&nb\&\u003Ewindow\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003E__LIB\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&s2\&\u003E\&react\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E]\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s2\&\u003E\&react\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&nb\&\u003Ewindow\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003E__LIB\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&s2\&\u003E\&react-addons-css-transition-group\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E]\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s2\&\u003E\&react-addons-css-transition-group\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&c1\&\u003E\u002F\u002F ...其它依赖包\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E我们在这里把一些第三方库注册到了 \u003Cb\u003Ewindow.__LIB\u003C\u002Fb\u003E 下,这些库可以作为底层的基础库,免于重复打包。\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E然后执行 \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Ewebpack lib-bundle.js lib.js\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E得到打包好的 \u003Cb\u003Elib.js\u003C\u002Fb\u003E。然后去设置我们的 externals :\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Ewebpack\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'webpack'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&nx\&\u003Emodule\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003Eexports\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n\u003Cspan class=\&nx\&\u003Eexternals\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s1\&\u003E'react'\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'window.__LIB[\&react\&]'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s1\&\u003E'react-addons-css-transition-group'\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'window.__LIB[\&react-addons-css-transition-group\&]'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F 其它库\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F其它配置忽略...... \u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E};\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E这时由于 externals 的存在,Webpack 打包的时候就会避开这些模块超多,依赖关系复杂的库,把这些第三方 module 的入口指向预先打包好的\u003Cb\u003E lib.js\u003C\u002Fb\u003E 的入口 \u003Cb\u003Ewindow.__LIB\u003C\u002Fb\u003E,从而只打包我们的业务代码。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E终极解决方法\u003C\u002Fh2\u003E\u003Cp\u003E上面我们提到的方法本质上就是一种\u003Cb\u003E“动态链接库(dll)”\u003C\u002Fb\u003E的思想,这在 windows 系统下面是一种很常见的思想。\u003Cb\u003E一个 dll 包,就是一个很纯净的依赖库,它本身不能运行,是用来给你的 app 或者业务代码引用的。\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E同样的 Webpack 最近也新加入了这个功能:\u003Cb\u003Ewebpack.DllPlugin\u003C\u002Fb\u003E。使用这个功能需要把打包过程分成两步:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E 打包ddl包\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E 引用ddl包,打包业务代码\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E首先我们来打包ddl包,首先配置一个这样的 \u003Cb\u003Eddl.config.js\u003C\u002Fb\u003E:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kr\&\u003Econst\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Ewebpack\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'webpack'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&kr\&\u003Econst\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Evendors\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s1\&\u003E'react'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s1\&\u003E'react-dom'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s1\&\u003E'react-router'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F ...其它库\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&nx\&\u003Emodule\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003Eexports\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Eoutput\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Epath\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'build'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Efilename\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'[name].js'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Elibrary\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'[name]'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E},\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Eentry\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s2\&\u003E\&lib\&\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Evendors\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E},\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Eplugins\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003Enew\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Ewebpack\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003EDllPlugin\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E({\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Epath\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'manifest.json'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Ename\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'[name]'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Econtext\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003E__dirname\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E}),\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E],\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E};\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\u003Cb\u003Ewebpack.DllPlugin\u003C\u002Fb\u003E 的选项中:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003Epath \u003C\u002Fb\u003E是 \u003Cb\u003Emanifest.json\u003C\u002Fb\u003E 文件的输出路径,这个文件会用于后续的业务代码打包;\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003Ename \u003C\u002Fb\u003E是 dll 暴露的对象名,要跟 \u003Cb\u003Eoutput.library\u003C\u002Fb\u003E 保持一致;\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003Econtext \u003C\u002Fb\u003E是解析包路径的上下文,这个要跟接下来配置的 webpack.config.js 一致。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E运行Webpack,会输出两个文件一个是打包好的 \u003Cb\u003Elib.js\u003C\u002Fb\u003E,一个就是 \u003Cb\u003Emanifest.json\u003C\u002Fb\u003E,它里面的内容大概是这样的:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s2\&\u003E\&name\&\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s2\&\u003E\&vendor_ac51ba426d4f259b8b18\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s2\&\u003E\&content\&\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s2\&\u003E\&.\u002Fnode_modules\u002Freact\u002Freact.js\&\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&mi\&\u003E1\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s2\&\u003E\&.\u002Fnode_modules\u002Freact\u002Flib\u002FReact.js\&\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&mi\&\u003E2\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s2\&\u003E\&.\u002Fnode_modules\u002Freact\u002Fnode_modules\u002Fobject-assign\u002Findex.js\&\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&mi\&\u003E3\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s2\&\u003E\&.\u002Fnode_modules\u002Freact\u002Flib\u002FReactChildren.js\&\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&mi\&\u003E4\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s2\&\u003E\&.\u002Fnode_modules\u002Freact\u002Flib\u002FPooledClass.js\&\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&mi\&\u003E5\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&s2\&\u003E\&.\u002Fnode_modules\u002Freact\u002Flib\u002FreactProdInvariant.js\&\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&mi\&\u003E6\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F ............\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E接下来我们就可以快乐地打包业务代码啦,首先写好打包配置文件 \u003Cb\u003Ewebpack.config.js\u003C\u002Fb\u003E:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kr\&\u003Econst\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Ewebpack\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'webpack'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&nx\&\u003Emodule\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003Eexports\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Eoutput\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Epath\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'build'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Efilename\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'[name].js'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E},\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Eentry\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Eapp\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&s1\&\u003E'.\u002Fsrc\u002Findex.js'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E},\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Eplugins\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003Enew\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Ewebpack\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003EDllReferencePlugin\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E({\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Econtext\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003E__dirname\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nx\&\u003Emanifest\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Erequire\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s1\&\u003E'.\u002Fmanifest.json'\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E),\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E}),\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E],\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E};\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\u003Cb\u003Ewebpack.DllReferencePlugin\u003C\u002Fb\u003E 的选项中:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003Econtext \u003C\u002Fb\u003E需要跟之前保持一致,这个用来指导 Webpack 匹配 \u003Cb\u003Emanifest.json\u003C\u002Fb\u003E 中库的路径;\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003Emanifest \u003C\u002Fb\u003E用来引入刚才输出的 \u003Cb\u003Emanifest.json\u003C\u002Fb\u003E 文件。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cb\u003EDllPlugin\u003C\u002Fb\u003E 本质上的做法和我们手动分离这些第三方库是一样的,但是对于包极多的应用来说,自动化明显加快了生产效率。\u003C\u002Fp\u003E&,&updated&:new Date(&T06:51:58.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:60,&collapsedCount&:0,&likeCount&:340,&state&:&published&,&isLiked&:false,&slug&:&&,&lastestTipjarors&:[],&isTitleImageFullScreen&:false,&rating&:&none&,&titleImage&:&https:\u002F\\u002F897c2df2f21aa6b2f9f4a_r.png&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&reviewers&:[],&topics&:[{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&webpack&},{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&JavaScript&},{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&前端开发&}],&adminClosedComment&:false,&titleImageSize&:{&width&:1920,&height&:960},&href&:&\u002Fapi\u002Fposts\u002F&,&excerptTitle&:&&,&column&:{&slug&:&starkwang&,&name&:&一只码农的技术日记&},&tipjarState&:&activated&,&tipjarTagLine&:&谢谢OvO&,&sourceUrl&:&&,&pageCommentsCount&:60,&tipjarorCount&:0,&annotationAction&:[],&hasPublishingDraft&:false,&snapshotUrl&:&&,&publishedTime&:&T14:51:58+08:00&,&url&:&\u002Fp\u002F&,&lastestLikers&:[{&bio&:&QE&,&isFollowing&:false,&hash&:&c03ef14e31e91930d05dbfdd32aea6a8&,&uid&:28,&isOrg&:false,&slug&:&yeyongwx&,&isFollowed&:false,&description&:&&,&name&:&叶勇&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fyeyongwx&,&avatar&:{&id&:&da8e974dc&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&计算机网络在读、自学ui设计师&,&isFollowing&:false,&hash&:&f29e1b940b3cdcf9d770bc&,&uid&:332700,&isOrg&:false,&slug&:&sabarlily&,&isFollowed&:false,&description&:&&,&name&:&sabarlily&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fsabarlily&,&avatar&:{&id&:&v2-dbe33b25e1dd617c851fd49f&,&template&:&https:\u002F\\u002F50\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&web前端开发&,&isFollowing&:false,&hash&:&188bc78a0f1e535d13f965b&,&uid&:08,&isOrg&:false,&slug&:&lemon-yezi&,&isFollowed&:false,&description&:&曾担任珍爱网web前端开发,热爱前端,热爱旅行&,&name&:&柠檬&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Flemon-yezi&,&avatar&:{&id&:&v2-2ab165ac2b&,&template&:&https:\u002F\\u002F50\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&腾讯SNG前端,个人博客:https:\u002F\u002Fcpselvis.github.io\u002F&,&isFollowing&:false,&hash&:&0d41abbdb09&,&uid&:416500,&isOrg&:false,&slug&:&cpselvis&,&isFollowed&:false,&description&:&开源中国讲师,现腾讯IVWEB团队,Github: https:\\u002Fcpselvis&,&name&:&cpselvis&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fcpselvis&,&avatar&:{&id&:&v2-f4f893a395fdb2016eb3&,&template&:&https:\u002F\\u002F50\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&饿了&,&isFollowing&:false,&hash&:&7de63a250f4b14ec69d129ef458a36ff&,&uid&:04,&isOrg&:false,&slug&:&zhang-xiang-di&,&isFollowed&:false,&description&:&&,&name&:&张翔迪&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fzhang-xiang-di&,&avatar&:{&id&:&80dfe654c&,&template&:&https:\u002F\\u002F50\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false}],&summary&:&\u003Cimg src=\&https:\u002F\\u002Fd5aacfff08af1dca999e_200x112.png\& data-rawwidth=\&780\& data-rawheight=\&124\& class=\&origin_image inline-img zh-lightbox-thumb\& data-original=\&https:\u002F\\u002Fd5aacfff08af1dca999e_r.png\&\u003E这几天写腾讯实习生 Mini 项目的时候用上了 react 全家桶,当然同时引入了 Webpack 作为打包工具。但是开发过程中遇到一个很棘手的问题就是,react 加上 react-router、material-ui、superagent、eventproxy 这些第三方轮子一共有好几百个 module,Webpack …&,&reviewingCommentsCount&:0,&meta&:{&previous&:{&isTitleImageFullScreen&:false,&rating&:&none&,&titleImage&:&https:\u002F\\u002F50\u002Fa48eedb38a417c57a3171aec1d10dd0b_xl.jpg&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&topics&:[{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&函数式编程&},{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&JavaScript&}],&adminClosedComment&:false,&href&:&\u002Fapi\u002Fposts\u002F&,&excerptTitle&:&&,&author&:{&bio&:&企鹅培育与饲养&,&isFollowing&:false,&hash&:&d965f32ae58ad3a48a1585a4&,&uid&:68,&isOrg&:false,&slug&:&starkwei&,&isFollowed&:false,&description&:&\u002Fstarkwang&,&name&:&Stark伟&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fstarkwei&,&avatar&:{&id&:&v2-cfafbe3e362ecf37f1d5fcb7f488f13c&,&template&:&https:\u002F\\u002F50\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},&column&:{&slug&:&starkwang&,&name&:&一只码农的技术日记&},&content&:&\u003Cp\u003E一、引言\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E说到函数式编程,大家可能第一印象都是学院派的那些晦涩难懂的代码,充满了一大堆抽象的不知所云的符号,似乎只有大学里的计算机教授才会使用这些东西。在曾经的某个时代可能确实如此,但是近年来随着技术的发展,函数式编程已经在实际生产中发挥巨大的作用了,越来越多的语言开始加入闭包,匿名函数等非常典型的函数式编程的特性,从某种程度上来讲,函数式编程正在逐步“同化”命令式编程。\u003C\u002Fp\u003E\u003Cp\u003EJavaScript 作为一种典型的多范式编程语言,这两年随着React的火热,函数式编程的概念也开始流行起来,RxJS、cycleJS、lodashJS、underscoreJS等多种开源库都使用了函数式的特性。所以下面介绍一些函数式编程的知识和概念。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E二、纯函数\u003C\u002Fh2\u003E\u003Cp\u003E如果你还记得一些初中的数学知识的话,函数 \u003Cb\u003Ef\u003C\u002Fb\u003E 的概念就是,对于输入 \u003Cb\u003Ex\u003C\u002Fb\u003E 产生一个输出 \u003Cb\u003Ey = f(x)\u003C\u002Fb\u003E。这便是一种最简单的纯函数。\u003Cb\u003E纯函数的定义是,对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E下面来举个栗子,比如在Javascript中对于数组的操作,有些是纯的,有些就不是纯的:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-js\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nx\&\u003Earr\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E1\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E2\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E3\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E4\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E5\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&c1\&\u003E\u002F\u002F Array.slice是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的\u003C\u002Fspan\u003E\n\u003Cspan class=\&c1\&\u003E\u002F\u002F 可以,这很函数式\u003C\u002Fspan\u003E\n\u003Cspan class=\&nx\&\u003Exs\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&nx\&\u003Eslice\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E0\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E3\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&c1\&\u003E\u002F\u002}

我要回帖

更多关于 webview 内存占用过大 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信