webpack manifest.jsbuild后生成的app,vendor,manifest三者有何职能不同

webpack使用小记 | 进击的马斯特
如果前两年大家还在讨论grunt和gulp等构建工具的话,现在无疑是的时代。严格来讲,webpack其实和grunt/gulp根本不是一种东西,它不是一个构建工具,而是module bundler。简单来说,webpack将JS、CSS、HTML(包含各种预处理器)以及图片等等都视为“资源”,每个资源文件都是一个module文件,而module文件之间存在依赖,webpack就是根据module文件间的依赖将所有module打包(bundle)起来。而回忆我们用grunt/gulp构建项目时,做的很大一部分工作也无非是将JS、CSS、HTML编译合并压缩等等,所以从这个层面上讲用webpack和grunt/gulp得到的结果是一样的。但webpack好就好在使用loader的概念让配置更加容易,再也不用和一堆文件路径打交道了。这篇文章就讲一下自己在使用webpack时的一些实践。
使用npm管理所有依赖说到前端的依赖管理,可能很多人脑海里第一印象还是,其实bower更像是一个依赖下载工具,你还是需要手动将这些下载好的文件用script标签引用到页面中去(尽管有帮我们自动化),所以它其实并不能真正的做到“依赖管理”。另外,bower据说已经要停止开发了,与此同时,越来越多的前端项目都开始支持npm,支持使用node的CommonJS机制来发行它们的前端库。这样在前端项目的package.json中不仅需要devDependencies,长期受冷落的dependencies也要用起来了。前者用来声明一些build过程中需要用的到一些构建工具,而后者用来声明开发使用到的前端库。webpack虽然支持多种包管理机制,但CommonJS应该还是最推荐的吧。用npm将要使用的库安装好后,直接在js文件中require即可。当然,使用ES6的话(需配合Babel),import它也是支持的哦。这样一来,第三方库的管理,项目中文件间的依赖管理再也不是问题了,可以像写node以及其他后端语言那样畅快的引用来引用去了。
说到依赖管理,还有个问题不得不提。package.json文件中采用node的SemVer来管理版本号,默认的^符号会匹配最新的minor版本。简单的说,你安装的时候可能是^1.4.3,但是一旦1.5.0发布,你运行npm install就会装1.5.0,也就是说它只会保留第一个数字。所以有需要的话可以把^改为~,它的限制更严格一些,~1.4.3最多只会安装到1.4.9,可以最大程度的减少升级带来的兼容性问题。有人会想,有没有类似python的pip freeze功能把依赖锁死在指定的版本上呢?当然!试试npm shrinkwrap吧,它会将当前安装在node_modules下的依赖版本写死在生成的node-shrinkwrap.json文件中,将这个文件加到git里,别人使用npm install安装依赖包时就可以保证装到和你一模一样的版本了。
善用yargs,避免多个配置文件很多github上的webpack starter项目都提供了多个webpack.config.js:如webpack.prod.config.js和webpack.dev.config.js,目的就是为了适配不同的环境。其实开发和发布的配置大部分是相同的,所以里面会有很多重复的配置。还有的项目看到了这一点,来了一个webpack.make.config.js提供默认的大部分配置,然后在其他的配置文件里require这个文件,做相应的更改。反正都是提供多个webpack配置文件,然后不同的环境用不同的。有必要这么复杂吗?在前面的文章中我反复提到一个概念,虽然xxx.config.js是配置文件,但是里面可以包含任何的node逻辑,所以为什么不试试只维护一个配置文件,针对不同的环境传不同的参数呢?像下面这样:
1234"scripts": {
"start": "webpack-dev-server --mock",
"build": "rm -rf ./build && webpack --progress --mock --prod"}
默认会查找名为webpack.config.js的配置文件,然后在这个文件中我们去处理自定义的参数即可。这里又要安利了,一个非常便捷的命令行参数处理工具。具体可以参考的讲解和它的文档,这里不再赘述。
常用插件webpack的配置可以说就是module和plugins的配置,module里主要就是配置各种loaders,没啥可说的,你要require什么类型的文件就去搜相应的loader就好,这一节主要说后面的这个:plugins的配置。webpack支持非常多的插件,详见,下面就讲一些常用的场景会用到的插件。
将jQuery全局暴露虽然很多人已经不提倡再使用jQuery,但很多第三方的库依然依赖jQuery,如果我们要使用这些第三方的库,那么我们还是没法摆脱jQuery。那么正常情况下我们只要保证jQuery的script标签在第三方库前面即可,因为jQuery中会暴露$给全局(window.$)使用。但使用了webpack后情况就稍显复杂了,作为module bundler,默认是不会有任何module暴露给全局的。这样,如果第三方库的代码中出现$.xxx或jQuery.xxx或window.jQuery或window.$则会直接报错。要达到类似的效果,则需要使用webpack内置的ProvidePlugin插件,配置很简单,只需要:
12345new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery"})
这样,当webpack碰到require的第三方库中出现全局的$、jQeury和window.jQuery时,就会使用node_module下jquery包export出来的东西了。
给JS定义全局flag在JS代码中我们常常需要一些静态的flag来标识是开发环境还是生产环境,一个简单的例子就是,开发环境你的log可能是直接console里打出来就好,而生产环境中就需要将log上传到统一的地方方便查询。也就是说,有些代码可能只是为了生产环境而存在的,而有些代码只为开发环境存在。我在为生产环境编译打包的时候就需要移除这些没用的代码,移除的操作可以胜任,但前提是它只移除类似if (false) {...}的代码片段,所以我们要做的就是定义全局的布尔flag,在build的时候传入不同的参数来控制它是true还是false。这时就需要使用webpack内置的DefinePlugin插件了:
12345new webpack.DefinePlugin({
__PROD__: args.prod,
__MOCK__: args.mock}),
上面我们同样使用了yargs,这样在build时传入--prod,则变量__PROD__即为true,传入--mock则__MOCK__为true。在JS代码中就可以使用类似的判断if (__PROD__) {...}了。
第三方库输出单独的JS一般我们为生产环境打包JS时,不管源文件有多少个,一般都是输出两个最终文件:app.js和vendor.js(LazyLoad的情况另说),前者是我们自己编写的代码合并压缩而成,后者是我们使用的第三方库合并压缩后的结果。使用webpack实现这个需要也很简单,使用webpack内置的CommonsChunkPlugin即可,我们要做的就是两步:
在entry中定义app和vendor这两个模块:
1234567891011entry: {
'source/app/index.js'
'angular',
'angular-ui-router',
'angular-animate'
在plugins里使用该插件:
1new monsChunkPlugin('vendor', isProd ? 'vendor.[hash].js' : 'vendor.js')
这样,所有模块中只要有require到vendor数组中定义的这些第三方模块,那么这些第三方模块都会被统一提取出来,放入vendor.js中去。在插件的配置中我们还进行了判断,如果是生产环境则给最终生成的文件名加hash。
将CSS由style内嵌变成独立.css文件在处理CSS时webpack需要两个不同的loader:css-loader和style-loader,前者负责将CSS文件变成文本返回,并处理其中的url()和@import(),而后者将CSS以style标签的形式插入到页面中去。一般使用的时候这么写:
1234{
test: /\.styl$/,
loader: 'style!css?sourceMap!autoprefixer!stylus'}
上面的loader配置中,我们处理所有的styl文件(stylus),先用stylus-loader将stylus变成CSS,然后使用autoprefixer-loader添加各种浏览器兼容性前缀,最后使用css-loader和style-loader。但这样的问题就是所有CSS文件都是直接以style标签的形式插入页面,不利于缓存,我们还是更习惯build之后能够输出单独的.css文件。这时就需要webpack的插件了,注意它不是内置的,需要额外安装。它的使用也很简单,分两步:
在loaders定义中指定:
1234{
test: /\.styl$/,
loader: ExtractTextPlugin.extract('style', 'css?sourceMap!autoprefixer!stylus')}
在plugins中使用插件:
123var ExtractTextPlugin = require("extract-text-webpack-plugin");new ExtractTextPlugin(isProd ? '[name].[hash].css' : '[name].css')
同样,这里我们在生产环境使用了hash命名。
copy指定文件到指定路径按理说用了webpack后所有的模块都通过require来管理了,用不着这样针对某个文件的copy了。这确实是一个不太常见的场景,比如说使用ES6的项目中,即便使用了Babel,但它并不会翻译类似Symbol这样的关键字,造成IE的兼容性问题:IE里会直接报“Symbol not defined”错误。这个时候我们就需要在页面中引入Babel里自带的shim文件了,babel-core中有一个browser-polyfill.min.js文件,是浏览器直接可以include的文件,并不需要使用webpack把它包一层。所以我们要做的其实是build的时候将这个文件copy到指定的目录,然后在index.html中包含这个文件即可。这个时候就需要这个插件了,配置起来也是非常的简单:
12345var CopyWebpackPlugin = require('copy-webpack-plugin');new CopyWebpackPlugin([
{ from: 'node_modules/babel-core/browser-polyfill.min.js', to: 'polyfill.js'}])
这样文件就会被复制到webpack的output.path配置的路径下了。
定义入口HTML文件单页应用还是需要一个入口的index.html文件的,这个文件的功能就是include所有的JS、CSS文件,然后body里指定一个ng-view或ui-view即可。这么机械化有规律的动作当然有插件来帮我们做了,那就是。最简单的配置就是直接在plugins直接定义:
123var HtmlWebpackPlugin = require('html-webpack-plugin');new HtmlWebpackPlugin()
默认的配置会生成一个结构非常简单的index.html文件,HTML的title是“Webpack App”,然后entry中定义的bundle生成的JS会以script标签包在body里,CSS会以link标签包在head里。显然,默认的模板是没法满足我们的需求的,我们需要给插件传入参数进行配置:
title和filename:控制生成页面的title和文件名。
inject:控制JS插入的位置,body或者head。
favicon:可以指定一个favicon.ico文件路径,作为网站的图标。
chunks和excludeChunks:一个数组,包含你需要引入或排除的bundle的名字(定义在entry中)。
template:自定义模板。
这里我们重点来说一说template这个配置,因为Angular的场景是在body里需要一个ui-view的,所以我们必须要自定义模板。template不仅支持直接传入HTML文件的路径,也支持各种模板语言,来看看这个定义:
123456new HtmlWebpackPlugin({
template: 'jade!./source/app/index.jade',
chunks: ['app', 'vendor'],
favicon: 'favicon.ico',
appName: appName}),
在template中我们指定了jade模板文件,然后配上jade-loader。这里注意的是,如果你想向jade文件中传参数,类似jade?foo=bar!xxx.jade,貌似是不支持的。那怎么实现这个需求呢?比如我想让build的时候动态决定index.html中的ng-app名字,怎么办呢?上面的appName: appName就是答案。在传给插件的配置中,除了它自身支持的配置,我们可以传入任意名称的键值对,然后在jade文件中可以这样访问到:
1html(lang=&en&, ng-app= htmlWebpackPlugin.options.appName)
除了htmlWebpackPlugin变量外,在自定义模板中,还可以访问webpack和webpackConfig,前者可以获取webpack的stats,如webpack.hash获取本次webpack编译得到的hash码,后者可以获取webpack的所有配置。这两个变量在一些场景下也是非常有用的。最后还是一点要说,html-webpack-plugin支持生成多个HTML文件,只要在plugins中定义多个new htmlWebpackPlugin({...})即可。
-p参数的坑-p参数是官方推荐的Production shortcut,意思就是为生产环境build时请使用webpack -p,那么-p指什么?文档里说了,代表--optimize-minimize和--optimize-occurence-order这两个命令行参数。先来说说第二个,在里解释了,webpack会把所有的chunk当做资源进行编号,这个插件可以保证出现次数多的资源编号小一些,短一点,这样有助于减小最终的文件大小。再来看看第一个--optimize-minimize,在webpack的中没有找到。没关系,我们直接搜源码,可以发现,在webpack/bin/convert-argv.js(1.12.14版本)中有这么一段代码:
12345ifBooleanArg("optimize-minimize", function() {
ensureArray(options, "plugins");
var UglifyJsPlugin = require("../lib/optimize/UglifyJsPlugin");
options.plugins.push(new UglifyJsPlugin());});
也就是说,这个参数实际上会加载UglifyJsPlugin这个插件,而且是无参(默认参数)加载的。这里坑就来了!如果你的ES6代码中出现类似这样的定义:
1234567import MockData from './e2e.data';angular.module('appTest', [])
.service(MockData.name, MockData)class MockData {...}export default MockD
也就是说,使用module的.name属性来决定一个service的名字,然后在别的地方使用依赖注入来引入这个service的话,这个时候一旦你使用-p参数,程序就会报错:找不到provider,MockDataProvider &- MockData。因为在e2e.data.js文件中你export的class虽然叫MockData,但这个名字会被UglifyJsPlugin改掉。这是因为这个插件有一个mangle选项,会对所有函数名变量名进行混淆,在压缩的同时保证安全。这倒没什么,坑就坑在,webpack自己的文档中告诉大家这个mangle的option默认是flase的,也就是说不会混淆。但在UglifyJsPlugin插件的源码里明明写着if(options.mangle !== false) {...},除非你传入一个flase,默认确实是会混淆的。你说这误导不误导,我在Github上提了个issue:,虽然官方没管,但最新的文档中貌似已经修复了这个错误。所以我的建议是:不要使用-p参数!想要什么自己定义就好:
12345678910111213if (isProd) {
plugins.push(
new webpack.NoErrorsPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
mangle: false
new webpack.optimize.OccurenceOrderPlugin()
有了上面的定义,在build的时候只要使用webpack --prod就好。还有一点值得一说,如果你使用了-p,即便你额外定义了一个自己的mangle=false的UglifyJsPlugin插件在webpack配置里,也是不起作用的。所以,还是那句话,不要使用-p参数。
轻松实现LazyLoad使用webpack的可以非常方便的实现LazyLoad。对于大型的应用来说,一次性将所有文件全部下载进浏览器显得很不划算,因为有些功能可能不太常用,不需要一开始就加载。Code Splitting功能可以在代码中定义“分割点”,即代码执行到这些点时才会去加载所需的模块,这样就实现了按需加载的LazyLoad。下面就以Angular和ui-router为例来看看具体怎么LazyLoad。假设我们的网站有4个大页面(每个页面里可能有很多子页面),我们希望路由到某个页面时再去加载这个页面所需要的模块。要实现这个LazyLoad,我们要明确两点:
“分割点”设在哪?
一个页面需要的模块包括HTML、CSS、JS,具体怎么去加载这三种类型?
第一个问题很简单,由于我们的想法是在路由到某个页面时再去加载所需模块,所以很自然的,我们会想把“分割点”放在路由的定义中。放在路由中还可以帮我们解决第二个问题,因为路由中通常需要定义路由到这个页面时的template和controller,这恰恰是我们上面提到的HTML和JS部分。下面我们就来看看具体怎么操作吧。
通过templateProvider动态加载模板我们可以在定义路由的时候在template中去动态加载所需的模板,但是template参数是个string,显然不满足我们的要求(templateUrl显然也不适合我们的场景)。这里就需要ui-router的了,它的值可以是一个函数,然后在这个函数中支持返回一个promise,由这个promise最终来返回HTML字符串。来看看代码:
123456789101112131415$stateProvider.state('root.layout.phone', {
views: {
url: '/phone',
'main@root': {
templateProvider: ['$q', ($q) =& {
return $q((resolve) =& {
require.ensure([], () =& {
resolve(require('./phone.jade'));
}, 'phone');
controller: 'PhoneController as vm'
}});
上面的例子中,我们想要在访问/phone这个路由时再去加载phone.jade这个template。来看看templateProvider的配置,首先它的函数返回一个promise(第6行),这个promise我们直接使用$q的构造函数,这个构造函数接受两个参数:resolve和reject,分别用来resolve和reject这个promise。然后在这个promise中我们要resolve的就是phone.jade这个template的字符串内容,使用jade-loader配置后,require('./phone.jade')返回的就是jade编译后的HTML字符串了(第8行)。那么什么时候去resolve呢?这里我们重点来看看第7行:webpack的require.ensure函数,我们就是使用这个函数来实现“Code Splitting”的,它有三个参数:
依赖的模块数组:模块名组成的数组,webpack会先加载这些模块再执行后面的回调函数。
回调函数:在模块数组加载完以后才会执行这个。
chunk的名字:从这个“分割点”分割出去的模块会放入额外的模块,这个参数指定模块的名字。这个参数主要用于存在多个require.ensure的情况,我们可以将多个“分割点”分割出去的代码放入同一个模块。
为什么我们在第7行使用一个空数组呢?其实我们可以写require.ensure(['./phone.jade'], () =& {resolve(require('./phone.jade'));}, 'phone'),但因为我们在回调里立马又require了这个jade,所以外面不写是无所谓的。
使用resolve来动态加载所需JS说完了templateProvider,我们再来看看下面的controller参数,controller参数与平常的写法并没有什么区别(第12行),因为没有一个叫controllerProvider的参数来让我们做类似的事情。那controller的JS模块在哪里动态加载呢?别忘了我们有resolve啊,每个路由定义都可以有一个resolve,只有这个resolve的promise被resolve了(有点绕了。。。)才会真正进入到那个路由中去,所以这正是我们加载controller和phone模块依赖的其它模块的好时机。来看看代码:
12345678910resolve: {
loadModule: ['$q', '$ocLazyLoad', ($q, $ocLazyLoad) =& {
return $q((resolve) =& {
require.ensure([], () =& {
$ocLazyLoad.load({name: require('./index').name});
resolve();
}, 'phone');
}]}
我们为这个resolve取名loadModule,因为它的作用就是加载phone模块自身的所有JS文件以及它所依赖的其它模块,并不需要我们真正的去return什么,所以第6行我们直接resolve了空值。另外注意第7行我们同样使用了phone作为chunk名,这保证了这个“分割点”分割出去的代码和上面分割出去的template是在一起的。那么JS依赖具体是怎么加载进来的呢?全靠第5行的./index.js文件了,来看看看这个./index.js的定义:
123456789101112131415161718import angular from 'angular';import PhoneController from './phone.controller';import PhoneAddController from './add/phone-add.controller';import PhoneDetailController from './detail/phone-detail.controller';import PhoneService from './phone.service';import phoneForm from '../../components/phone-form';import phoneTable from '../../components/phone-table';export default angular.module('app.pages.phone', [
phoneForm.name,
phoneTable.name])
.controller(PhoneController.name, PhoneController)
.controller(PhoneAddController.name, PhoneAddController)
.controller(PhoneDetailController.name, PhoneDetailController)
.service('PhoneAPI', PhoneService);
可以看到,这个文件除了将phone模块自身的一些controller/service引入进来,还引入了它所依赖的component模块:phoneForm和phoneTable,所以只要require了它就可以保证phone的所有依赖都引进来了。最后,你肯定奇怪第5行的写法了,这里为什么需要$ocLazyLoad呢?是一个适用于Angular的Lazyload的库,其实我们只用它就可以实现Angular的LazyLoad,但这里我们用它的作用并不是动态加载文件,而是使webpack动态加载进来的文件中的Angular的module名app.pages.phone生效。因为Angular是不允许动态去声明一个module的,我们需要使用这种方法来使动态加载进来的文件中包含的Angular的module生效。
讲完了HTML和JS,那CSS怎么办呢?其实有了css-loader我们完全可以把它放在JS中或HTML中通过require(xxx.css)加载进来,因为通常CSS是与某个页面或某个模块绑定的(公用的样式除外),所以放在某个HTML中或某个JS模块中来require它也是合情合理的。
这样一来,phoen模块依赖的所有HTML、JS、CSS都动态加载进来了,在真正build的时候,会生成一个名为phone.js的文件(可以使用webpack的output.chunkFilename来配置生成的chunk文件的名称规则)来包含这些东西,而这个文件只有你第一次访问/phone的时候才会动态加载进来,这一点可以访问 并打开console来验证。
style-loader的坑这样设置了以后,所有动态加载的HTML、CSS和JS都会放在最终生成的phone.js中,也就是说,webpack目前是不支持为chunk生成单独的CSS文件的,我提了个issue:,但目前还没回应。这会产生什么问题呢?如果你要动态加载的CSS中含有图片路径,比如:
1background url("../../components/_layout/logo.png") top center no-repeat
那么最终得到的CSS中路径虽然是正确的:url(assets/images/logo.46e30a699c7cdacd7e86.png) top center no-repeat(具体路径与你的file-loader配置有关),但Chrome会将这个路径解析为:chrome-devtools://devtools/bundled/assets/images/logo.46e30a699c7cdacd7e86.png,因为CSS文件是以动态bundle的形式加载进来的,所以图片资源的相对路径是以bundle为准的,这将导致图片无法正常的显示。已经有人在style-loader的项目里提了bug:,但目前style-loader还没有修复。好消息是Vue的作者fork了一份style-loader并,所以暂时大家可以使用代替style-loader来解决这个问题。
最后,完整的实现可以看我Github上的项目。vendor,build后生成的app,manifest三者有何职能不同,webpack_操作系统_神洲手机数码站
vendor,build后生成的app,manifest三者有何职能不同,webpack
编辑: 神洲手机数码站 &&&来源:用户发布&&&发布时间:&&&查看次数:49
谁了解vendor,build后生成的app,manifest三者有何职能不同,webpack,谢了。网友回答item表示迭代参数 比数组通request.setAttribute(&array&,xxx)放入迭代内容${requestScope.array}var 相于引用面用${array.xxx1}${array.xxx2}数组值输
操作系统相关
更多相关内容
本站内容来自网友发布,本站无法保证其部分内容的正确性,请用户一定仔细辨别。
[] &&[联系QQ:885&971&98] &
沪ICP备号&Webpack&React (九) 构建Kanban
构建Kanban
到目前为至,我们已经有了一个运行良好的Kanban应用, 本章的目地是在此基础上设置不错的产品级的版本.使用各种技术来控制打包后的大小并加入浏览器端缓存.
优化构建大小
我们运行npm run build,能看到下面的结果|:
Hash: 807faffbf966eb7f08fc
Version: webpack 1.12.13
Time: 3967ms
Chunk Names
+ 337 hidden modules
1.12MB的打包大小是很大的!但我们能通过几种手段减少打包后的体积,我们可以压缩我们的脚本,也能让React优化它自身.同样也可以使用gzip进行内容压缩.
压缩可以转化我们的代码到一个较小的格式而不会失去原本的功能.通常是使用一些预置的代码进行格式转换.有时这样做可能会在不经意间破坏代码,这就是为什么我们在store中明确的指定id的原因了.
最简单的压缩方式是调用webpack -p(-p 为 production).其它方式,为了获得更多的控制我们可以直接使用插件. 默认的Uglify将输出大量的警告并且在这个情况不会提供有用的东西,我们将禁用它.增加下面的部分到我们的Webpack的配置:
webpack.config.js
if(TARGET === 'build') {
leanpub-start-delete
module.exports = merge(common, {});
leanpub-end-delete
leanpub-start-insert
module.exports = merge(common, {
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
leanpub-end-insert
当我们现在执行npm run build会看到下面的信息:
Hash: ff80bbb1bdd7df271313
Version: webpack 1.12.13
Time: 9159ms
Chunk Names
+ 337 hidden modules
考虑到使用插件后会增加工作,它需要较长的时间.但从好的方面说构建后的包要小的多.
设置process.env.NODE_ENV
我们能进一步的压缩构建的体积.React依赖process.env.NODE_ENV进行优化.如果我们设置React到production, React将以优化了的方式进行构建, 这样做会禁用一些检查(如,属性类型检查).最重要的是它可以减小包的体积并提升性能.
在Webpack配置中,可以增加下面代码到我们的插件部分:
webpack.config.js
if(TARGET === 'build') {
module.exports = merge(common, {
plugins: [
leanpub-start-insert
// Setting DefinePlugin affects React library size!
// DefinePlugin replaces content "as is" so we need some extra quotes
// for the generated code to make sense
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
// You can set this to JSON.stringify('development') for your
// development target to force NODE_ENV to development mode
// no matter what
leanpub-end-insert
这样做很有用,如果在执行时我们有一部分代码被判定为false,这个压缩后的构建将移除这一部分.
再次运行npm run build,你能看到被优化的结果:
Hash: cde2c1861fbd65f03c3b
Version: webpack 1.12.13
Time: 9032ms
Chunk Names
+ 333 hidden modules
我们的构建的包从最初的1.12MB到368KB,最后成为307KB.如果我们在用gzip压缩它.体积大概还会减少40%.并且gzip被浏览器很好的支持.
我们还能稍稍的改善一下.我们可以分离app和vendor包,并且为他们增加的文件名增加hash值.
分离app和vendor包
分离我们的应用到两个特定的包的好处是可以让我们更好的用客户端缓存.例如.我们可以只需要频繁的修改app包.在本例中,假设vendor包已经被加载了,客户端将只获取app包.
这个方案不会和加载单独的包一样快,因为他有一个额外的请求.感谢客户端缓存技术,我们可以不需要在每次请求时都重载所有数据.如果有某些包没有改变,将不会重新加载.本例中app被修改后只会下载这个包.
定义一个vendor入口点
当开发这个应用的时候,我们会分离我们的dependencies和devDependencies.这样做会变得很方便.它让我们只把dependencies加入打包.这样可以避免我们把开发环境相关的包(如Webpack)加到最终的构建中.
如果你打开package.json,dependencies列表会包含:alt, alt-container, alt-utils, node-uuid, react, react-addons-update, react-dnd, react-dnd-html5-backend, 和 react-dom如果你还有其它的包则需要将它们移到devDependencies中.
我们还需要定义一个vendor入口点.考虑到alt-utils在执行时会有一些问题,我们可以简单的从vendor包中把它排除出去.你可以用相似的手段处理其它可能有问题的依赖.
webpack.config.js
const path = require('path');
const merge = require('webpack-merge');
const webpack = require('webpack');
const NpmInstallPlugin = require('npm-install-webpack-plugin');
leanpub-start-insert
const pkg = require('./package.json');
leanpub-end-insert
const TARGET = process.env.npm_lifecycle_
const PATHS = {
app: path.join(__dirname, 'app'),
build: path.join(__dirname, 'build')
process.env.BABEL_ENV = TARGET;
const common = {
app: PATHS.app
resolve: {
extensions: ['', '.js', '.jsx']
path: PATHS.build,
leanpub-start-delete
filename: 'bundle.js'
leanpub-end-delete
leanpub-start-insert
filename: '[name].js'
leanpub-end-insert
if(TARGET === 'build') {
module.exports = merge(common, {
leanpub-start-insert
vendor: Object.keys(pkg.dependencies).filter(function(v) {
return v !== 'alt-utils';
leanpub-end-insert
plugins: [
这告诉Webpack我们想从entry中分离出vendor级的依赖.
除此之外,可以使用动态加载require.ensure来达到目地.
在此执行构建脚本npm run build,经过一段时间会看到下面的结果:
{1} [built]
现在我们分离出了app和vendor包.如果你检查这些文件,你会发现一些问题,app.js包含vendor的所有依赖.我们需要让Webpack避免这种情况.这就需要设置CommonsChunkPlugin了
设置CommonsChunkPlugin
CommonsChunkPlugin让我们可以提取我们vendor中所需的代码.此外我们使用它提取一个manifest,这个文件让Webpack知道怎么映射哪个模块使用哪个文件.我们下一步需要基于此来建立一个长期的缓存.设置如下:
webpack.config.js
if(TARGET === 'build') {
module.exports = merge(common, {
// Define vendor entry point needed for splitting
plugins: [
leanpub-start-insert
// Extract vendor and manifest files
new monsChunkPlugin({
names: ['vendor', 'manifest']
leanpub-end-insert
运行npm run build会看到下面的输出:
{1} [built]
现在可以看到app包对比vendor包要小的多.这不是分开的目地,我们最终要在客户端缓存它们.要达到这个目地,我们需要在文件名中加入一个hash值.
增加hash值到文件名
Webpack提供占位符来让我们使用不同类型的hash值.最有用的是:
[name] - 反回入口名称
[hash] - 反回创建的hash
[chunkhash] - 反回一段特定的hash
使用这个占位符你可以得到文件名如:
app.d587bbd6e38337f5accd.js
vendor.dc746a5db4ed.js
如果文件内容是不同的,hash也将随之改变,这就会清理客户端对它的缓存,更准确的说是浏览器将会发送一个新的请求.意味着如果只有app发生改变,则只需要再次请求这个文件.
我们能使用占位符像如下配置:
webpack.config.js
if(TARGET === 'build') {
module.exports = merge(common, {
// Define vendor entry point needed for splitting
leanpub-start-insert
path: PATHS.build,
filename: '[name].[chunkhash].js',
chunkFilename: '[chunkhash].js'
leanpub-end-insert
plugins: [
现在你运行npm run build,会看到如下结果:
Hash: 7ddb226a3bc
Version: webpack 1.12.13
Time: 8741ms
Chunk Names
app.5b758fea66f30faf0f0e.js
vendor.db9ab3d83c.js
manifest.19a8ace3.js
[0] multi vendor 112 bytes {1} [built]
+ 333 hidden modules
现在我们的文件增加了hash值,为了验证它是工作的,你可以尝试改变app/index.jsx并写一个console.log,之后再次运行build,应该会看到只有app和manifest相关的包是被改变的.
其它方式改善构建的方式是通过CDN的方式加载一些流行的依赖,如React.这样会进一步减少vendor包的体积.意味着如果用户使用早期的CDN,缓存同样生效.
通过html-webpack-plugin生成index.html
虽然现在我们有了一个不错的包,但还是有一个问题.我们当前的index.html不能正确的指向构建的包.我们必需手动添加脚本标签,幸运的是有一个较好的方式.通过Node.js API触发Webpack.构建输出它的API包含了这些资源名子.之后把它们加入模板中.
其它好的方案是使用Webpack的插件和为此目地设计的模板.html-webpack-plugin和html-webpack-template一起为我们提供帮助并可以为我们执行大量繁重的工作.首先安装它们:
npm i html-webpack-plugin html-webpack-template --save-dev
为了加入它到我们的工程,我们需要调整我们的配置.在些期间,我们可以移除build/index.html系统会自动生成它:
webpack.config.js
leanpub-start-insert
const HtmlWebpackPlugin = require('html-webpack-plugin');
leanpub-end-insert
const common = {
leanpub-start-delete
leanpub-end-delete
leanpub-start-insert
plugins: [
new HtmlWebpackPlugin({
template: 'node_modules/html-webpack-template/index.ejs',
title: 'Kanban app',
appMountId: 'app',
inject: false
leanpub-end-insert
if(TARGET === 'start' || !TARGET) {
module.exports = merge(common, {
devtool: 'eval-source-map',
devServer: {
leanpub-start-delete
contentBase: PATHS.build,
leanpub-end-delete
plugins: [
new webpack.HotModuleReplacementPlugin(),
new NpmInstallPlugin({
save: true // --save
现在运行npm run build,输出的文件中会包含一个index.html:
Hash: 7ddb226a3bc
Version: webpack 1.12.13
Time: 9200ms
Chunk Names
app.5b758fea66f30faf0f0e.js
vendor.db9ab3d83c.js
manifest.19a8ace3.js
index.html
[0] multi vendor 112 bytes {1} [built]
+ 333 hidden modules
Child html-webpack-plugin for "index.html":
+ 3 hidden modules
尽管在我们的项目中添加了一些配置,我们现在不必担心整合的事情。如果需要更多的灵活性,也可以实现一个自定义的模板。
当前的配置没有清理构建目录的功能.我们可以增加这个插件清理这个目录.安装并且如下配置它:
npm i clean-webpack-plugin --save-dev
webpack.config.js
leanpub-start-insert
const CleanPlugin = require('clean-webpack-plugin');
leanpub-end-insert
if(TARGET === 'build') {
module.exports = merge(common, {
plugins: [
leanpub-start-insert
new CleanPlugin([PATHS.build]),
leanpub-end-insert
After this change, our build directory should remain nice and tidy when building. See clean-webpack-plugin for further options.
修改后,build目录能保持干净,其它选项可以看
我们现在已经有了一个好很的构建配置,但要怎么处理CSS呢?根据我们的配置,它被内联到JavaScript中!虽然在开发过程中能带来方便.但它不是一个好主意.因为这样做不利于我们缓存这些CSS.在一些情况下可能会出现文档使样式短暂失效(FOUC)的问题.
Webpack提供一个分CSS包的方法.可以使用.它运行在编译期,并且在HMR(热插拔)中不好使.因为我们只是用它来发布打包.所以是没问题的.
需要一些配置才能使它工作:
npm i extract-text-webpack-plugin --save-dev
下一步,还需要移除我们在common中关于CSS的相关声明,之后需要分离build和dev配置如下:
webpack.config.js
leanpub-start-insert
const ExtractTextPlugin = require('extract-text-webpack-plugin');
leanpub-end-insert
const common = {
app: PATHS.app
resolve: {
extensions: ['', '.js', '.jsx']
loaders: [
leanpub-start-delete
// Test expects a RegExp! Note the slashes!
test: /\.css$/,
loaders: ['style', 'css'],
// Include accepts either a path or an array of paths.
include: PATHS.app
leanpub-end-delete
test: /\.jsx?$/,
loaders: ['babel?cacheDirectory'],
include: PATHS.app
plugins: [
new HtmlWebpackPlugin({
template: 'node_modules/html-webpack-template/index.html',
title: 'Kanban app',
appMountId: 'app',
inject: false
if(TARGET === 'start' || !TARGET) {
module.exports = merge(common, {
devtool: 'eval-source-map',
devServer: {
leanpub-start-insert
loaders: [
// Define development specific CSS setup
test: /\.css$/,
loaders: ['style', 'css'],
include: PATHS.app
leanpub-end-insert
plugins: [
if(TARGET === 'build') {
module.exports = merge(common, {
leanpub-start-insert
loaders: [
// Extract CSS during build
test: /\.css$/,
loader: ExtractTextPlugin.extract('style', 'css'),
include: PATHS.app
leanpub-end-insert
plugins: [
new CleanPlugin([PATHS.build], {
verbose: false // Don't write logs to console
leanpub-start-insert
// Output extracted CSS to a file
new ExtractTextPlugin('[name].[chunkhash].css'),
leanpub-end-insert
之后运行npm run build会看到如下信息:
clean-webpack-plugin: .../kanban_app/build has been removed.
Hash: 32b800b64e76fbd5d447
Version: webpack 1.12.13
Time: 9125ms
Chunk Names
app.91211bee0c.js
vendor.db9ab3d83c.js
manifest.c3a4ec998b7ccca9268f.js
app.91211bee0c.css
index.html
[0] multi vendor 112 bytes {1} [built]
+ 333 hidden modules
Child html-webpack-plugin for "index.html":
+ 3 hidden modules
Child extract-text-webpack-plugin:
+ 2 hidden modules
现在我们分离出了CSS文件.并且JavaScript包又小了一些.我们还能避免FOUC问题.浏览器不需要等JavaScript加载样式信息.反而它能单独处理CSS从而避免文档样式短暂失效(FOUC)的问题.
改善缓存行为
这是一个小问题,注意到生成的app.js和app.css的文件名区块相同.这意味着如果JavaScript和CSS改变相关的内容,这个hash值同样会改变.这不是我们想要的,因为它会使我们的缓存失效.即使我们不想这么做.
一种解决方式是把样式加到它自已的区块中.我们需要从当前的区块中解耦出样式,并定义它为自定义的区块通过如下配置:
app/index.jsx
leanpub-start-delete
import './main.css';
leanpub-end-delete
webpack.config.js
const PATHS = {
app: path.join(__dirname, 'app'),
leanpub-start-delete
build: path.join(__dirname, 'build')
leanpub-end-delete
leanpub-start-insert
build: path.join(__dirname, 'build'),
style: path.join(__dirname, 'app/main.css')
leanpub-end-insert
const common = {
leanpub-start-delete
app: PATHS.app
leanpub-end-delete
leanpub-start-insert
app: PATHS.app,
style: PATHS.style
leanpub-end-insert
现在运行npm run build会看到如下信息:
Hash: 22e7d6f1bcbb
Version: webpack 1.12.13
Time: 9271ms
Chunk Names
app.5d41daf7.js
style.0688e2aa1fa6c618dcdd.js
vendor.ec122d2dba.js
manifest.035b449d16a98df2cb4f.js
style.0688e2aa1fa6c618dcdd.css
index.html
[0] multi vendor 112 bytes {2} [built]
+ 333 hidden modules
Child html-webpack-plugin for "index.html":
+ 3 hidden modules
Child extract-text-webpack-plugin:
+ 2 hidden modules
这一步后,我们已经成功地从JavaScript分离出样式。更改它不应该影响JavaScript区块hash值,反之亦然.如果你仔细观察会发现一个文件名为style.64acd61995c3afbc43f1.js,它是Webpack生成的看起来如下:
webpackJsonp([1,3],[function(n,c){}]);
从技术上讲它是多余的。它可以在 HtmlWebpackPlugin 模板文件装载时被排除。但这种解决方案是不够好的。理想情况下 Webpack 根本不应该生成这些文件。
生成统计分析
生成统计分析是理解 Webpack 好的方式.看以看到我们包的构成.
为了得到结果我们需要调整一下我们的配置:
package.json
"scripts": {
leanpub-start-insert
"stats": "webpack --profile --json & stats.json",
leanpub-end-insert
现在执行npm run stats可以在你的工程根止录中看到* stats.json * ,我们能传递它到一个中查看更多结果.并可帮助你进一步优化输出.
npm run build提供了一些静态的托管.一个巧妙的方法是把它放到GitHub的页面上。
托管在 GitHub 上
我们可以使用gh-pages达到这个目地.首先把它指向到你的构建目录,之后把内容推到gh-pages分支,让我们执行
npm i gh-pages --save-dev
还需要一个入口点在package.json:
package.json
"scripts": {
leanpub-start-insert
"deploy": "gh-pages -d build",
leanpub-end-insert
如果你执行npm run deploy 并且没出现问题,你能把应用托管到GitHub页面.通过https://&name&.github.io/&project&(<// 到GitHub)
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?}

我要回帖

更多关于 webpack vendor作用 的文章

更多推荐

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

点击添加站长微信