基于webpack4的VUE多页脚手架

连接

——写在前面

一、为何要本身手写一个脚手架?

在写这个脚手架以前我也深深的问过本身,在我工做的项目中须要去从新写一个脚手架吗?或者说有那么多已经写好的脚手架为什么不采用?html

事情的通过是这样的,在很早很早之前,我尝试使用过VUE-CLI 2进行过项目开发,当时并不怎么熟悉webpack以及一些打包编译的相关知识,随着页面的增长!项目的体积的增大,致使总体build出来的包很是之大,公共文件也会随之增大,加载速度也会随之下降。后续的结果我就不作阐述了!vue

那么!后来... vue-Cli 3.0诞生了,首次使用简直是个救世的主,不管从速度仍是编译过程体验都很是好,并且还能够经过vue.config.js自定义不少的配置,基本上彻底能够自定义了,固然!也是随着页面的不断增长核项目的增大,在这个时候。我开始发现我本身对于webpack或者说打包编译的相关知识已经不能支撑我继续自定义的开发下去了。发现了一些潜在的问题,可是并无实际的解决思路的时候,就能够追述到一些基础知识的欠缺。node

随着项目的逐渐增大,尤为是多页应用的支持以及一些文件模块化的拆分,包括一些tree-shaking的运用。尽管vue-cli3.0支持configureWebpack 这样强大的API。可是仔细想一想,要想从事情的本质或根本上解决问题,首先自身要相对的熟悉,并在此基础之上运用和操做,得以充分的发挥;因此仍是决定本身去了解以便更好的开发。webpack

二、如何去思考遇到的问题?

在项目的开发中,尤为是在写脚手架这种工具性的东西的时候,须要考虑到的场景和实际运用的时候,更多的是不能沉浸在本身的思惟之中,参考并学习别人的经验是有必要的,从而得出一套符合本身的思路。ios

从最开始的目录结构,以及模块化的一些思考,如何更好的作到性能的优化等等,都是值得思考的问题所在,如何处理好本身的业务逻辑,针对不一样的项目以及兼容性的考虑等等。git

——正文

在此以前咱们须要对webpack4的一些文档或者API进行充分的了解,能够参考官方文档或者参考印记中文的webpack文档,可是针对于webpack4的文档原本介绍的不是很全面,在不少的API上面仍是以前的介绍,因此,有不少小伙伴在看文档的时候发现并不能正常的进行操做,这时候能够结合两个不一样版本的文档进行研究,固然时间的消耗成本也是比较高的。github

三、一些基本的构建思路!
在此以前我将控制业务逻辑的代码进行分离,脚手架是单独存在的。二者目录结构相互独立,业务逻辑的代码永远不会干涉到脚手架的

对于一些最基础的配置我就不一一讲述了:web

webpack.config.js

module.exports = {
    mode:'development',
    entry: './***.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    module:{
        //...
    },
    plugins:[
        //...
    ]
};

以上是一些基本的配置方式,固然咱们能够经过package.json文件中的scripts选项自定义一些基于命令行的配置:vue-router

package.josn

 "scripts": {
    "dev": "APP_ENV=dev webpack-dev-server --config core/dev.js",
    "prd": "APP_ENV=build webpack --config core/build.js",
    "build": "APP_ENV=build webpack --config core/build.js",
    "lint": "eslint --fix */*.vue *.js",
    "test:whale": "jest tests/*.js && npm run build"
  },

以上的一些配置仅供参考,分别能够经过npm run dev | npm run prd | ...来进行相关的操做。关于更多的npm script 详细参考

四、node_modules的一些介绍

关于node_modules这块其实很是的庞大,这也是npm的一个巨大的社区。与其说是学习,不如说是抽一部分优秀的包来使用,要知道一个node_modules包是在作什么事情,能够经过npm搜索包进行浏览其详细介绍,在对应的github地址进行相应的了解。

关于如何学习node_modules包并不在个人介绍范围内,可是我会介绍一些经常使用的一些比较好用的包进行分享。并且在构建脚手架以前,必需要对不少的包进行相关的学习,不须要知道每一个包的源码是什么,可是至少须要知道一些包的做用和用法,好比.vue文件须要使用vue-loader进行解析,使用到一些语法校验的时候须要用到eslint,基于webpack进行生成html页面的时候须要用到html-webpack-plugin;ES6ES5须要用到一系列的@babel/xxx插件,等等..

我这里不作一一的介绍,可是会在后续用到每个包的时候作相对的介绍便可。

——多入口的输入和编译的输出

多入口的输入相对比较简单,能够直接参考官方文档
固然entry能够接受一个对象进行多页面的输入,若是只是起步阶段建议使用一个入口文件进行编写,例:

module.exports = {
    entry: index:'xxx/xxx/index.js'
}
////或
module.exports = {
    mode: isProd ? 'production' : 'development',
    entry: {
        index:'xxx/xxx/index.js'
    }
}

输出能够根据本身的须要配置output参数:

let path = require('path');
output: {
    filename: './js/[name].js',
    chunkFilename: './js/[name].js',
    path: path.resolve(__dirname, '../build/')
},
关于 module | plugins | optimization | ...等模块的配置我就不详细的说了,可是主要仍是要说一下各个模块之间的配合使用。
五、module模块的优化

首先module.noParse是一个必备的参数,能够忽略一下大型的已经构建过的模块,从而提升构建的性能,这里放一个案例:

webpack.config.js

module:{
    noParse:/^(vue|vue-router|vuex|vuex-router-sync|lodash|echarts|axios)$/,
}

以上的案例忽略了vue | vue-router | vuex | ...等大型额已经构建过的模块。(通俗易懂的说一下,webpack在打包时会将全部用到的模块进行打包编译,在这里只是忽略了从新构建的过程,可是对于chunk的时候依然仍是会在内存堆里面使用并打包。)

六、比较复杂的module.rules

其实相对来讲,在webapck 4module.rules仍是和以前同样的使用,针对不一样的后缀的文件进行不一样的处理,在这里主要说一下借助了happypack进行多线程处理,固然你也能够从npm网站详细了解。与此同时我也选择放弃使用DllPlugin | DLLReferencePlugin插件,下面我就说一下,如何选择?为什么放弃?

咱们都知道webpack在打包编译时是单线程的运做,可是咱们的电脑已经很是强大,单线程的处理,第一是随着项目的增大时间会增加,第二是有些空闲的cpu等硬件设备得不到充分的利用,这是有咱们选择开启多线程的操做,在node中是能够开启多线程的,只是咱们借助了happypack这样的工具来进行多线程的控制和使用,在webpack中,咱们可能须要处理到.js文件,也可能会用到.css文件,那么在打包的时候会将全部的字符汇聚到内存中,进行大量的密集型运算,并提取拆分红不一样的块,这时候咱们借助多线程来处理便可,使用方法以下:

首先在当前项目中执行命令行npm i happypack -D在开发环境中安装,引入:

const webpack = require('webpack');

例如咱们再处理.js文件时候须要使用cache-loaderbabel-loader时,只须要配置:

{
    test: /\.js$/,
    use: ['happypack/loader?id=babel' ],
    exclude: /node_modules/
}

并在plugins选项中配置:

new HappyPack({
    id: 'babel',
    cache: true,
    threads: require('os').cpus().length, //开几个线程去处理 
    loaders: [ 'cache-loader','babel-loader?cacheDirectory' ]
    verbose: true,         //容许 HappyPack 输出日志 ,默认true
    //threadPool: happyThreadPool,
})

须要注意的只是rules中调用的HappyPackidplugins中实例化的id相同便可。

最后说一下为什么要放弃DllPlugin,这个插件是生成一个动态的连接文件,也就是你把你认为不须要屡次重复编译的文件经过DllPlugin插件如生成一个.js.json文件,当你第二次进行打包编译的时候再经过DllReferencePlugin进行引入使用,这样就会大大减小了编译的数量,好处是能够多一些固定的模块包进行减小处理,可是后来我发现,在项目中只有模块拆分的足够细致时候这个确实有很多做用,不然徒增一些步骤,由于每次在拆分块的时候,不少的模块是会进行从新组装的。(以上只是我的观点,仅供参考~)

七、介绍几个简单的plugins用法

若是是在开发环境,咱们须要对页面进行热更新,咱们能够开启:

plugins:[
    new webpack.NamedModulesPlugin(),
    new webpack.HotModuleReplacementPlugin()
]

NamedModulesPlugin是现实热更新的模块的名称,HotModuleReplacementPlugin启用HRM官方有介绍
若是你是使用VUE开发项目,那么new VueLoaderPlugin()是必不可少的了。
注入一些全局变量使用参考:

new webpack.DefinePlugin({
    //...
}),

重点的说一下html-webpack-plugin这个插件,用于生成过一些页面的小伙伴应该都有所了解,那么在生成多个页面的时候,咱们就须要new HtmlWebpackPlugin({ ... })多个实例便可,一般状况下,咱们会经过循环入口文件进行循环注入一个数组中便可。
另外备注一部分参数的说明:

new HtmlWebpackPlugin({
    title: PAGES[k].title || 'title',
    chunks: chunks,
    filename:`${k}.html`,
    minify: {
        removeComments: true,       //Strip HTML comments
        collapseWhitespace: true,   //折叠有助于文档树中文本节点的空白区域
    },                    //对html进行压缩,默认false
    hash: PAGES[k].hash === true ? true : false,      //默认false
    template: PAGES[k].template,
    excludeChunks: excludeChunks,
    favicon:PAGES[k].favicon || ''
    // chunksSortMode:"dependency"
    /**
     * 'dependency' 按照不一样文件的依赖关系来排序。
     * 'auto' 默认值,插件的内置的排序方式,具体顺序我也不太清楚...
     * 'none' 无序? 不太清楚...
     * 'function' 提供一个函数!!复杂...
     */
})

上面的代码只是截取的部分代码片断,有一些值是须要作一些相应的处理,关于HtmlWebpackPlugin插件的详细参考

这里须要注意的是,当咱们对项目包中的公共代码作了不一样的 splitChunks(下面会讲解这个模块)时候,好比像 chunks默认会所有注入进入页面,因此我么你可能须要手动进行一些处理,或者使用 excludeChunks对一些块进行排除,其排除的是你最终生成的代码文件名称。 template是指对应的模版。更加详细的参考github 文档等。
八、敲黑板、讲重点的optimization.splitChunks

固然目前这部分的文档在官网还不是很全,因此这里咱们参考了印记中文webpack的说明文档,optimization指优化模块。splitChunks能够翻译为拆分块,默认的配置参数参考官方文档便可,重要的说一下optimization.splitChunks.cacheGroups,很是强大的一个API,先说何时会用到这个功能,这就对应了咱们最前面所说的,vue-cli3脚手架不太方便的地方,当项目包逐渐增大的同时,一般状况下,会为咱们提供一个公共文件,在vue-cli3脚手架中,为咱们提取了 vendor-chunks.js为全部文件的公共文件,可是若是咱们有一个场景,其中的某一个页面根本不须要依赖一些包的,且这个包相对较大的同时,咱们另可多发一次请求,也不须要去加载这些多余的文件,咱们就能够经过optimization.splitChunks.cacheGroups将这部分公共的提取出来,在对制定的页面在HtmlWebpackPlugin插件中将它排除便可(或者采用注入指定的chunk的形式),举一个例子,咱们能够吧全部页面中的vue相关的源码包提取到一个单独的文件,咱们能够采用以下的配置:

optimization: {
    splitChunks: {
        minSize: 30000,
        //缓存组
        cacheGroups: {
            vue: {
                test: /([\/]node_modules[\/]vue)/,  // <- window | mac -> /node_modules/vue/
                name: 'vue-vendor',                 //拆分块的名称
                chunks: 'initial',                  //initial(初始块)、async(按需加载块)、all(所有块),默认为all;
                priority: 100,                      // 该配置项是设置处理的优先级,数值越大越优先处理
                enforce: true,                      // 若是cacheGroup中没有设置minSize,则据此判断是否使用上层的minSize,true:则使用0,false:使用上层minSize
                //minSize: 1024*10,                 //表示在压缩前的最小模块大小,默认为0;
                //minChunks: 1,                     //表示被引用次数,默认为1;
                //maxAsyncRequests:                 //最大的按需(异步)加载次数,默认为1;
                //maxInitialRequests:               //最大的初始化加载次数,默认为1;
                //reuseExistingChunk: true          //表示可使用已经存在的块,即若是知足条件的块已经存在就使用已有的,再也不建立一个新的块。
            }
        }
    },

}

固然你能够根据本身的须要进行多个的配置,名称能够自定义,test是过滤的方式,这里核心要说明的是priority(优先权),固然是数字越大优先权越高,什么意思,当咱们在进行webpack打包的同时,会将咱们全部用到的代码所有加载在内存中,进行进行转换和编译等操做,拆分块的核心在于,将一些公共的模块拆分红多个模块,按照优先级进行提取出去,生成一个文件,而后再去查找下一个优先级的进行提取。这里如何进行包拆分能够借助webpack-bundle-analyzer插件进行可视化的进行操做。其用法是直接npm i webpack-bundle-analyzer

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

webpack中配置plugin选项新增便可:

new BundleAnalyzerPlugin({
    defaultSizes:'gzip',
    logLevel:'warn'
}),

在执行项目的时候会进行包依赖的详细分析,此时咱们能够经过可视化的方式进行更好的拆分。

splitChunks也是在webpack4鼎力打造的一个功能,在于理解其中的用法,拆分的代码块更多的时候是要咱们在html-webpack-plugin的时候更好的生成每个独立的页面,有一些框架中采用了每增长一个文件就回去自动添加一个新的页面的方式,固然这样的额缺点是必须按照对应的目录进行生成对应的页面,这里咱们采用了手动配置的方式生成新的页面,每生成一个页面,会默认的注入一些拆分出来的代码块。
虽然html-webpack-plugin没生成一个新的html页面必需要实例化一个新的对象。因此咱们能够经过配置文件的循环进行生成对应的配置:

new HtmlWebpackPlugin({
            title: PAGES[k].title || 'title',
            chunks: chunks,
            filename:`${k}.html`,
            minify: {
                removeComments: true,       //Strip HTML comments
                collapseWhitespace: true,   //折叠有助于文档树中文本节点的空白区域
            },    //对html进行压缩,默认false
            hash: PAGES[k].hash === true ? true : false,      //默认false
            template: PAGES[k].template,
            excludeChunks: excludeChunks,
            favicon:PAGES[k].favicon || ''
            // chunksSortMode:"dependency"
            /**
             * 'dependency' 按照不一样文件的依赖关系来排序。
             * 'auto' 默认值,插件的内置的排序方式,具体顺序我也不太清楚...
             * 'none' 无序? 不太清楚...
             * 'function' 提供一个函数!!复杂...
             */
        })

上面是一些脚手架中的源代码,在一些简单的页面,能够经过排除的方式省去一些文件的加载,而不是通用的加载一个较大的文件包。在优化的过程当中咱们能够减小http请求,可是咱们也能够减小请求包的大小。

放弃使用DllPlugin | DLLReferencePlugin

通过一段时间的考量,我发现关于DllPlugin & DLLReferencePlugin这两个插件的使用只有在一部分状况下才比较适合,当你的项目包中用一个不须要从新构建的模块的时候你再使用这个插件是比较合适的,然而不少时候,咱们每次的构建几乎都会从新编译咱们的代码,固然他们的使用方式是先经过DllPlugin去打包好不不须要从新构建的文件,同时生成manifest.json文件,在下次编译的同时经过DLLReferencePlugin进行载入便可,减小了一些包的重复编译。

总结

写在最后,这一块所包含的信息量相对较多,同时须要对项目构建有必定程度的了解,在不少的过程当中是须要去思考一个问题的解决方法和方式,而不只仅是追求使用,每个工具都会提供强大的API和功能以适合众多的业务场景。对于一些经过不一样的方式获得一样结果的问题就仁者见仁吧!

相关文章
相关标签/搜索