dahong icon indicating copy to clipboard operation
dahong copied to clipboard

boarder,快速开始一个webpack项目-优化篇

Open shaodahong opened this issue 7 years ago • 9 comments

在介绍篇#7 中,我大概说了下boarder这个命令行工具初始化出来的webpack项目模板有哪些功能,但是这些功能实在简单,所以想了想继续优化下,顺便解决下上篇留下来的问题

很多道友知道webpack打包现在很热门,但是不知其究竟,说实话我也不知道,上完班回去撸几把王者农药岂不快哉?但是最近webpack3又出来了,难道它想争夺版本帝?

本篇点到为止,可以根据关键字自行搜索,互联网的资料比我的更详细,易懂,有不明白的可以下面评论交流切磋,道友请留步!!!

上一篇中我已经介绍了目录结构,现在有所增强了

.
├── README.md
├── build
│   ├── webpack.base.js
│   ├── webpack.build.js
│   └── webpack.dev.js
├── dist
│   ├── index.html
│   └── static
│       └── js
├── package-lock.json
├── package.json
└── src
    ├── assets
    ├── index.html
    ├── index.js
    └── pages

可以看到src目录下增加pages这个文件夹,所以现在这个模板是支持多页面的,现在SPA大放异彩,但是有些项目中或多或少的都会涉及到其他的页面,为了方便,我们当然更希望一个npm run build后就可以发布上线了

好了,废话不多说,正题开始:

css预编译支持(stylus|less|sass)

很多项目书写css已经不是.css文件了,大多都是styl,scss和less了,所以我也要来支持下,在webpack.base.js中添加对应的loader,例如:

{
      test: /\.styl/,
      use: ExtractTextPlugin.extract({
            fallback: 'style-loader',
            use: [
                 'css-loader',
                 'stylus-loader'
             ]
      }),
}

前提是你需要npm install对应的loader

ExtractTextPlugin是为了分离css的,但是开发中切勿使用,别问我怎么知道的,我的热更新啊,我的泪啊

所以它提供了一个参数可以让我们可以来禁用它,disable就是来决定是否启用,我们根据环境来判断,环境是什么,提供个关键字给你node process

new ExtractTextPlugin({
      filename: 'static/css/[name].[contenthash].css',
      allChunks: true,
      disable: isPro ? false : true
})

css预编译可以使用了,并且可以利用ExtractTextPlugin提供给我们的contenthash做长效缓存,至于hash和css的contenthash和js的chunkhash的区别自行了解,大家需要知道一般不用hash,开发中直接使用[name].js这种就行

多页面支持

假如我们除了主页还有一个aboutsetting页面,那么pages目录结构应该是这样的

├── about
│   ├── index.html
│   └── index.js
└── setting
    ├── index.html
    └── index.js

我们多页面的页面数实际开发中是不可预料的,所以我们按照约定的结构实现自动化即可,我是一个有经验的程序

/**
 * 
 * 
 * @param {string} globPath 
 * @returns {object}
 */
function getEntries(globPath) {
    var files = glob.sync(globPath),
        entries = {};

    files.forEach(function (filepath) {
        var split = filepath.split('/');
        var name = split[split.length - 2];

        entries[name] = filepath;
    });

    return entries;
}

var entries = getEntries('./src/pages/*/index.js');
var hot = 'webpack-hot-middleware/client?reload=true';

entries['index'] = './src/index.js'

我们利用glob来找到这个多页面的主入口(index.js),并且以文件夹的名字作为webpack entry的入口,最后再手动加上主页index的入口,运行起来的entries应该是这样的

{ 
about: './src/pages/about/index.js',
setting: './src/pages/setting/index.js',
index: './src/index.js' 
}

找到入口之后我们要添加到webpack的配置中

baseConfig = {
   entry: {},
   ……
}

var hot = 'webpack-hot-middleware/client?reload=true';

    Object.keys(entries).forEach(function (name) {
        baseConfig.entry[name] = isPro ? entries[name] : [hot, entries[name]];
        var htmlPlugin = new HtmlWebpackPlugin({
            filename: name + '.html',
            template: name === 'index' ? './src/index.html' : './src/pages/' + name + '/index.html',
            inject: true,
            chunksSortMode: 'dependency',
        });
        baseConfig.plugins.push(htmlPlugin);
    })

这样的话我们webpack的入口就会有index,about和setting的入口,如果有更多的页面,放马过来

解决了第一个入口问题,那我们就要解决资源的加载问题了,我们理想的状态是多页面共享的依赖剥离出来,然后每个页面的依赖和业务代码再剥离出来,这样就会有这么些文件,index.[chunkhash] .js,vendor.[chunkhash].js,index.vendor.[chunkhash].js等等,index.[chunkhash].js是主页面的业务代码,vendor.[chunkhash].js是多页面共享的依赖,index.vendor.[chunkhash].js是主页面的依赖,因为依赖的代码会很少更新,所以我们分离出来可以利用chunkhash来帮我们做到长效的缓存

长效缓存

chunkhash是根据模块的内容计算出来的hash,但是模块的ID是一个递增的数,我们并不确定依赖的数量,所以业务代码的变化也会导致依赖代码的chunkhash改变,所以我们可以使用webpack提供的HashedModuleIdsPlugin来帮我们稳定模块的ID,

new Webpack.HashedModuleIdsPlugin()

HashedModuleIdsPlugin是干什么的?HashedModuleIdsPlugin就是为了避免这种无顺序的模块ID,他是根据模块的相对路径来产生的,有了HashedModuleIdsPlugin就可以了么,还不行,因为webpack runtime代码块中是包含chunks IDchunkhash的,所以我们也要把webpack runtime代码抽出来

baseConfig.plugins.push(new Webpack.optimize.CommonsChunkPlugin({
    name: ['manifest'],
    minChunks: Infinity
}))

用一个manifest来保存webpack runtime代码,虽然会多一个文件,但是我们可以用长效缓存的优点来掩盖这一切,因为webpack runtime很少,我们没有必要单独去加载这个js,可以内联到html的头部,这个我找了下插件发现没有合适的,要么就是使用ejs,要么就是不容易变通,所以我自己借鉴了下webpack插件,写了个基于HtmlWebpackPlugin的内联资源插件html-webpack-inline-assets-plugin

new HtmlWebpackInlineAssetsPlugin({
    head: 'manifest.',
    // body: 'manifest.'
})

插件使用还是很简单的,npm install之后直接使用,两个参数,内联到head或者body中,参数是正则表达式用来匹配资源的

至此,基本上这个项目所涵盖的点都有设计,也会有不完美的地方,希望在实践中能够弥补,让它更具有扩展性

如果你有什么要求,可以去对应的项目中给我提issues 命令行工具boarder webpack模板wdeme 资源内联插件html-webpack-inline-assets-plugin

shaodahong avatar Jun 26 '17 05:06 shaodahong

最近刚开始试着用webpack来打包项目 ,getEntries() 这个方法很不错,动态添加entery,学习了,非常感谢你的分享!

billyhero avatar Jul 20 '17 06:07 billyhero

嗯嗯,这个boarder不好,相当于下载器,直接下载这个模板wdemo,package.json的一些参数没有改,还是wdemo的,所以不是很完善,我写这个主要就是平时想立即用webpack的时候没有好的脚手架,只能把以前的项目配置拿过来,很麻烦,所以写了个这个

shaodahong avatar Jul 20 '17 07:07 shaodahong

刚开始学习webpack。。小白一枚,请教下,在开发环境下,a链接跳转路径、图片引用路径,在build之后路径就不对了,这种问题该怎么处理呀

myhuangqiang avatar Sep 28 '17 09:09 myhuangqiang

@myhuangqiang 你在开发环境用的是相对路径还是绝对路径?你用绝对路径应该是可以的,相对路径的话要配置url-loader,你可以看我的url-loader配置

shaodahong avatar Sep 28 '17 11:09 shaodahong

@shaodahong 非常感谢,a标签跳页面应该是根据build之后的路径把,刚试了一把,要开发环境下的目录跟打包之后href地址是不一样的^-^

myhuangqiang avatar Sep 28 '17 11:09 myhuangqiang

@myhuangqiang 是的,但是你描述的不是很清楚, 你可以上代码看下。相对路径肯定是相对于你a标签的页面的,比如你是组件,但是你build后加载是在index.html里面的,那么它会相对这个index.html来找的

shaodahong avatar Sep 28 '17 11:09 shaodahong

开发环境下首页有个链接<a href="./setting/index.html">vvv</a>应该是会跳转到setting目录下的页面,但是会报找不到这个页面。写成vvv却能找到对应的页面

myhuangqiang avatar Sep 28 '17 12:09 myhuangqiang

<a href="./setting.html">vvv</a>

myhuangqiang avatar Sep 28 '17 12:09 myhuangqiang

@myhuangqiang 不会吧,<a href="./setting.html">vvv</a><a href="./setting/index.html">vvv</a>不是一个页面吧

shaodahong avatar Sep 28 '17 12:09 shaodahong