本文是我用webpack进行项目构建的实践心得,场景是这样的,项目是大型类cms型,技术选型是vue,只支持chrome,有诸多子功能模块,全部打包在一起的话会有好几MB,所以最佳方式是进行多入口打包。文章包含我探索的过程以及webpack在使用中的一些技巧,希望能给大家带来参考价值。

首先,项目打包策略遵循以下几点原则:

  1. 选择合适的打包粒度,生成的单文件大小不要超过500KB
  2. 充分利用浏览器的并发请求,同时保证并发数不超过6
  3. 尽可能让浏览器命中304,频繁改动的业务代码不要与公共代码打包
  4. 避免加载太多用不到的代码,层级较深的页面进行异步加载

基于以上原则,我选择的打包策略如下:

  1. 第三方库如vue、jquery、bootstrap打包为一个文件
  2. 公共组件如弹窗、菜单等打包为一个文件
  3. 工具类、项目通用基类打包为一个文件
  4. 各个功能模块打包出自己的入口文件
  5. 各功能模块作用一个SPA,子页面进行异步加载

各入口文件的打包

由于项目不适宜整体作为一个SPA,所以各子功能都有一个自己的入口文件,我的源码目录结构如下:

apps目录下放置各个子功能,如question和paper,下面是各自的子页面。components目录放置公共组件,这个后面再说。

由于功能模块是随时会增加的,我不能在webpack的entry中写死这些入口文件,所以用了一个叫做glob的模块,它能够用通配符来取到所有的文件,就像我们用gulp那样。动态获取子功能入口文件的代码如下:

/**
* 动态查找所有入口文件
*/
var files = glob.sync('./public/src/apps/*/index.js');
var newEntries = {}; files.forEach(function(f){
var name = /.*\/(apps\/.*?\/index)\.js/.exec(f)[1];//得到apps/question/index这样的文件名
newEntries[name] = f;
}); config.entry = Object.assign({}, config.entry, newEntries);

webpack打包后的目录是很乱的,如果你入口文件的名字取为question,那么会在dist目录下直接生成一个question.xxxxx.js的文件。但是如果把名字取为apps/question/index这样的,则会生成对应的目录结构。我是比较喜欢构建后的目录也有清晰的结构的,可能是习惯gulp的后遗症吧。这样也便于我们在前端路由中进行统一操作。也是一个小技巧吧,我生成的各入口文件的目录如下:

第三方库的打包

项目中用到了一些第三方库,如vue、vue-router、jquery、boostrap等。这些库我们基本上是不会改动源代码的,并且项目初期就基本确定了,不会再添加。所以把它们打包在一起。当然这个也是要考虑大小不超过500KB的,如果是用到了像ueditor这样的大型工具库,还是要单独打包的。

配置文件的写法是很简单的,在entry中配一个名为vendor的就好,比如:

entry: {
vendor: ['vue', 'vue-router', './public/vendor/jquery/jquery']
},

不管是用npm安装的还是自己放在项目目录中的库都是可以的,只要路径写对就行。

为了把第三方库拆分出来(用<script>标签单独加载),我们还需要用webpack的CommonsChunkPlugin插件来把它提取一下,这样他就不会与业务代码打包到一起了。代码:

new webpack.optimize.CommonsChunkPlugin('vendor');

公共组件的打包

这部分代码的处理我是纠结了好久的,因为webpack的打包思想是以模块的依赖树为标准来进行分析的,如果a模块使用了loading组件,那么loading组件就会被打包进a模块,除非我们在代码中用require.ensure或者AMD式的require加回调,显式声明该组件异步加载,这样loading组件会被单独打包成一个chunk文件。

以上两者都不是我想要的,理由参见文章开头的打包原则,把所有公共组件打包在一起是一个自然合理的选择,但这又与webpack的精神相悖。

一开始我想到了一招曲线救国,就是在components目录下建一个main.js文件,该文件引用所有的组件,这样打包main.js的时候所有组件都会被打包进来,main.js的代码如下:

import loading from './loading.vue';
import topnav from './topnav.vue';
import centernav from './centernav.vue'; export {loading, topnav, centernav}

有点像sass的main文件的感觉。使用的时候这样写:

let components = require('./components/main');

export default {
components: {
loading: (resolve) =>{
require(['./components/main'],function(components){
resolve(components.loading);
})
}
}
}

缺点是也得写成异步加载的,否则main.js还是会被打包进业务代码。

不过后来我又一想,既然vendor可以,为什么组件不可以用同样的方式处理呢?于是乎找到了最佳方法。 同样先用glob动态找到所有的components,然后写进entry,最后再用CommonsChunkPlugin插件剥离出来。代码如下:

/*动态查找所有components*/
var comps = glob.sync('./public/src/components/*.vue');
var compsEntry = {components: comps};
config.entry = Object.assign({}, config.entry, compsEntry);

要注意CommonsChunkPlugin是不可以new多个的,要剥离多个需要传数组进去,写法如下:

new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'components']
})

如此一来,components就和vendor一样可以用<script>标签引入页面了,使用的时候就可以随便引入了,不会再被重复打包进业务代码。如:

import loading from './components/loading';
import topnav from './components/topnav';

把这些文件塞进入口页面

之前说过我们的子功能模块有各自的页面,所以我们需要把这些文件都给引入进这些页面,webpack的HtmlWebpackPlugin可以做这件事情,我们在动态查找入口文件的时候顺便把它做了就行了,代码如下:

/**
* 动态查找所有入口文件
*/
var files = glob.sync('./public/src/apps/*/index.js');
var newEntries = {}; files.forEach(function(f){
var name = /.*\/(apps\/.*?\/index)\.js/.exec(f)[1]; //得到apps/question/index 这样的文件名
newEntries[name] = f; var plug = new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../public/dist/'+ name +'.html'),
chunks: ['vendor', name, 'components'],
template: path.resolve(__dirname, '../public/src/index.html'),
inject: true
});
config.plugins.push(plug);
});

子页面的异步载入

每个功能模块是作为一个SPA应用来处理的,这就意味着我们会根据前端路由来动态加载相应子页面,使用官方的vue-router是很容易实现的,比如我们在question/index.js中可以如下写:

router.map({
'/list': {
component: (resolve) => {
require(['./list.vue'], resolve);
}
},
'/edit': {
component: (resolve) => {
require(['./edit.vue'], resolve);
}
}
});

在webpack的配置文件中就无需再写什么了,它会自动打包出对应的chunk文件,此时我的dist目录就长这样了:

有一点让我疑惑的是,异步加载的chunk文件貌似无法输出文件名称,尽管我在output参数中这么配置:chunkFilename: '[name].[chunkhash].js',[name]那里输出的还是id,可能和webpack处理异步chunk的机制有关吧,猜测的。不过也无所谓的,反正能够正确加载,就是名字难看点。

--------更新于2016.10.11-------

为异步chunk命名的方法我找到了,需要两步。首先output中还是应该这么配置:chunkFilename: '[name].[chunkhash].js'。然后,利用require.ensure的第三个参数,可以为chunk指定名字。上面的代码修改为如下:

router.map({
'/list': {
component: (resolve) => {
// require(['./list.vue'], resolve);
require.ensure([], function(){
resolve(require('./list.vue'));
}, 'list');
}
},
'/edit': {
component: (resolve) => {
//require(['./edit.vue'], resolve);
require.ensure([], function(){
resolve(require('./edit.vue'));
}, 'edit');
}
}
});

这样list和edit这两个组件生成的chunk就有名字了,如下:

我个人还是偏好生成的chunk能带上名字,这样可读性好一些,便于调试和尽快发现错误。


以上就是一个大概的架子了,由于我也是刚刚开始探索webpack(之前gulp党),一边 实践一边分享吧,还有很多细节的东西没法细讲,我在本系列文章中慢慢道来吧。

webpack解惑:多入口文件打包策略的更多相关文章

  1. webpack快速入门——CSS文件打包

    1.在src下新建css文件,在css文件下新建index.css文件,输入以下代码 body{ background:pink; color:yellowgreen; } 2.css建立好后,需要引 ...

  2. webpack配置:css文件打包、JS压缩打包和HTML文件发布

    一.CSS文件打包 1.在src下新建css文件,在css文件下新建index.css文件,输入以下代码 body{ color:red; font-size:20px; } 2.css建立好后,需要 ...

  3. webpack 配置多入口文件,输出多出口文件

    const path = require('path') module.exports = { // 入口文件的配置项 entry: { // 入口文件 entry: './src/entry.js' ...

  4. webpack chunkFilename 非入口文件的命名规则 [转]

    官网的文档只理解了filename是主入口的文件名,chunkFilename是非主入口的文件名 filename应该比较好理解,就是对应于entry里面生成出来的文件名.比如: { entry: { ...

  5. (5/24) 模块化:实现快速CSS文件打包

    写在前面:为了兼容,此处的webpack版本为3.6.0.webpack-dev-server版本为2.9.7.css-loader版本为2.0.0.style-loader版本为0.23.1,若在下 ...

  6. webpack 学习2 入口(entry)和输入管理(output)

    在开始上代码之前,先让我们盘一盘什么是webpack中的入口和输入 入口 假设你现在手里有一个水龙头,然后十个人用水管从你这里拿水.你这个龙头就是水的入口,水管就是你和这些人的依赖联系.现在供水局的要 ...

  7. Webpack的基本配置和打包与介绍

    1. 前言 1.1 Webpack是什么 可能有很多的小伙伴对于这个Webpack既熟悉又陌生,有一些刚开始接触vue的小伙伴在对项目进行打包的时候经常会使用到npm run build来进行打包,但 ...

  8. 【JavaScript】 Webpack安装及文件打包

    背景 最近开启一个新项目,需要对前端的各类资源文件进行打包,经过多方调研后,决定使用webpack工具.但是网上的教程多是将webpack直接作为服务器使用,而我只是想将其作为单纯的资源打包工具而已. ...

  9. Webpack多入口文件、热更新等体验

    Webpack现今流行的前端打包工具,今儿本人也来分享下自己学习体验. 一.html-webpack-plugin 实现html模板文件的解析与生成 在plugins加入HtmlWebpackPlug ...

随机推荐

  1. Virtual Box配置CentOS7网络(图文教程)

    之前很多次安装CentOS7虚拟机,每次配置网络在网上找教程,今天总结一下,全图文配置,方便以后查看. Virtual Box可选的网络接入方式包括: NAT 网络地址转换模式(NAT,Network ...

  2. HashSet HashTable 与 TreeSet

    HashSet<T>类 HashSet<T>类主要是设计用来做高性能集运算的,例如对两个集合求交集.并集.差集等.集合中包含一组不重复出现且无特性顺序的元素. HashSet& ...

  3. AngularJS过滤器filter-保留小数,小数点-$filter

    AngularJS      保留小数 默认是保留3位 固定的套路是 {{deom | number:4}} 意思就是保留小数点 的后四位 在渲染页面的时候 加入这儿个代码 用来精确浮点数,指定小数点 ...

  4. HTML 事件(二) 事件的注册与注销

    本篇主要介绍HTML元素事件的注册.注销的方式. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流.事件委托 4. ...

  5. Unity 序列化 总结

    查找了 Script Serialization http://docs.unity3d.com/Manual/script-Serialization.html 自定义序列化及例子: http:// ...

  6. Android学习探索之Java 8 在Android 开发中的应用

    前言: Java 8推出已经将近2年多了,引入很多革命性变化,加入了函数式编程的特征,使基于行为的编程成为可能,同时减化了各种设计模式的实现方式,是Java有史以来最重要的更新.但是Android上, ...

  7. .NET 基础 一步步 一幕幕[面向对象之对象和类]

    对象和类 本篇正式进入面向对象的知识点简述: 何为对象,佛曰:一花一世界,一木一浮生,一草一天堂,一叶一如来,一砂一极乐,一方一净土,一笑一尘缘,一念一清静.可见"万物皆对象". ...

  8. 在你的ASP.NET MVC中使用查找功能

    在程序中,使用查找功能是少之不了.今天在ASP.NET环境下演示一回. 在cshtml视图中,有三个文本框,让用户输入关键词,然后点击最右连的“搜索”铵钮,如果有结果将显示于下面. Html: 表格放 ...

  9. celery使用的一些小坑和技巧(非从无到有的过程)

    纯粹是记录一下自己在刚开始使用的时候遇到的一些坑,以及自己是怎样通过配合redis来解决问题的.文章分为三个部分,一是怎样跑起来,并且怎样监控相关的队列和任务:二是遇到的几个坑:三是给一些自己配合re ...

  10. 【夯实Mysql基础】MySQL性能优化的21个最佳实践 和 mysql使用索引

    本文地址 分享提纲: 1.为查询缓存优化你的查询 2. EXPLAIN 你的 SELECT 查询 3. 当只要一行数据时使用 LIMIT 1 4. 为搜索字段建索引 5. 在Join表的时候使用相当类 ...