美文网首页让前端飞程序员
从搭建vue-脚手架到掌握webpack配置(三.多页面构建)

从搭建vue-脚手架到掌握webpack配置(三.多页面构建)

作者: JasonWild | 来源:发表于2018-01-15 22:07 被阅读726次

    前言

    上一期中我们通过引入了插件实现了不少功能——样式抽离、公共模块提取、代码压缩等等;本期开始讲讲可能会用到的第三方编译器的配置和多入口的优化。

    本期重点:postcss和.babelrc配置多入口多页面代码提取优化

    往期链接:

    从搭建vue-脚手架到掌握webpack配置(一.基础配置)

    从搭建vue-脚手架到掌握webpack配置(二.插件与提取)

    小小题外话

    本系列文章写到了第三篇,Jason我发现了这系列的文章有一个很大的缺陷,文章和前面的文章存在着耦合,可能会导致知识点存在线性的关联,对于有基础的朋友来说,又要从第一期开始阅读的话,比较不友好。
    所以后面的文章开始Jason会尝试独立知识点,尽量回归知识点的运用上;在文章的开头也会说明本期的主要内容;当然一些插件和loader的进阶使用还是要有基础的,初学者还是建议从头过一遍。

    使用postcss

    postcss介绍

    postcss官方的GitHub上还有中文的介绍。

    PostCSS 是一个允许使用 JS 插件转换样式的工具。 这些插件可以检查(lint)你的 CSS,支持 CSS Variables 和 Mixins, 编译尚未被浏览器广泛支持的先进的 CSS 语法,内联图片,以及其它很多优秀的功能。

    简单来说postcss就是一个css的转换器,有了postcss或许你就不用再用less和sass了,通过在postcss上添加插件可以组装出你需要的语法需求和功能(属性变量,父子嵌套,版本兼容等),在postcss上通常会用的插件有cssnext、Autoprefixer、postcss-import。甚至可以在postcss上用less或sass编译器

    用法

    这里就用添加Autoprefixer(自动兼容浏览器)为例简单讲讲postcss的配置方法,有几种配置方法

    • 使用配置文件配置 postcss.config.js或.postcssrc.js
    • 使用post-loader的时候通过options配置项配置

    建议是使用配置文件进行配置,这样可以在所有调用到postcss-loader的地方使用同样的配置

    !值得一提的是其实vue-loader是默认启用了postcss-loader的,所以vue-loader的官方文档里面有直接设置postcss配置项的选项。

    所以在有vue-loader的项目里面其实是不用手动安装npm install -save-dev postcss-loader的,不是vue项目就装吧。

    只要在css的loaders里面添加vue-style-loader,就会自动启用postcss了,如下

    {
        test:/\.less$/,
        use:[
            'vue-style-loader',
            'css-loader',
            'less-loader'
        ]
        })
    },
    {
        test:/\.vue$/,
        loader:'vue-loader',
        options:{
            loaders:{
                'css': [
                    'vue-style-loader',
                    'css-loader',
                ],
                'less': [
                    'vue-style-loader',
                    'css-loader',
                    'less-loader'
                ]
            },
            //postcss:{}//vue-loader还自带了这一选项,但是建议用配置文件进行配置
        }
    }
    

    引入 Autoprefixer :

    • 需要安装 autoprefixer插件哦:npm install --save-dev autoprefixer
    • 新建.postcssrc.js文件在跟目录下,内容如下
    module.exports = {
      "plugins": {
        //"postcss-import": {},
        // to edit target browsers: use "browserslist" field in package.json
        "autoprefixer": {}
      }
    }
    

    autoprefixer对应的对象{},是该插件的配置项,有需要可以查阅文档进行配置;postcss-import插件是@import是可以引本地的文件,如node_modules内的文件,英文好的去看看该插件介绍。既然vue-cli用了postcss-import我们也这样用吧,记得安装npm install --save-dev postcss-import

    autoprefixer会去检查package.json里的browserslist 配置项作为兼容浏览器版本的范围

    //在package.json上添加这一项
    "browserslist": [
        "> 5%",
        "last 2 versions",
        "not ie <= 8"
      ]
    

    我们这里就简单的引入了autoprefixer,没有做过多的配置,如果对postcss感兴趣的话可以去看看下面文档

    .babelrc配置

    在使用es6语法编写js的时候,我们都会在webpack上用babel-loader转换js文件,而.bablrc就是babel编译js时用的的规则和插件的配置规范

    像postcss一样在根目录下创建名为.babelrc的文件(没有.js后缀)

    {
      "presets": [
        ["env", {
           "modules": false ,
           "targets": {
            "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
          }
        }],
        "stage-3"
      ]
    }
    

    安装依赖:

    npm install --save-dev babel-presets-env babel-presets-stage-0 
    

    babel-preset-env,本依赖插件是设置编译环境的插件,如果直接引入不设置规则那实际它和引入babel-preset-latest一样,会囊括es2015、es2016、es2017。

    'modules':false:设置模块引用规则,可以设置成"amd" | "umd" | "systemjs" | "commonjs" | false, defaults to "commonjs",设置了false,就是用es6以上默认的规则。

    targets.browsers:设置兼容的浏览器范围

    tage-3依赖,ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个

    babel-presets-stage-0  babel-presets-stage-1
    babel-presets-stage-2  babel-presets-stage-3
    
    其他

    还可以在里面添加插件实现更多的功能,如添加react的jsx编译。像官方给出的示例代码一样

    {
      "presets":["env"]
      "plugins": ["transform-react-jsx"],
      "ignore": [
        "foo.js",
        "bar/**/*.js"
      ]
    }
    

    毕竟这不是babelrc的教程,更高级的使用可以去以下的链接去深入学习。

    链接

    好了,整个个系列到目前为止常常会用到的webpack配置、loader、插件、编译器都使用过了。

    后面开始就要按照需求,改进我们的自动化构建配置了。

    多页面、多入口

    很多用vue或者react工程环境都会默认是spa(单页应用),但是业界上也有很多项目倾向于使用vue等框架的组件化功能,但是并不引入router模块,而是使用传统的路由链接;尤其是视频网站、购物网站等pc端的网页是不会做成spa的。所以在一个工程中编辑和生成多个页面是必须的情况。

    在src目录下新建home.js文件(内容和main.js一样就行),在webpack的entry项设置多个入口是最方便快捷的方法。

    entry:{
        app:'./src/main.js',
        home:'./src/home.js'
    },
    output:{
        path:path.resolve(__dirname,'./dist'),
        filename:"js/[name].js",
    },
    

    entry对应不同的页面设置不同的入口文件,output.filename要写成[name]以chunk名命名的形式

    看似简单,但是我们之前用到公共代码提取(CommonsChunkPlugin)和css抽离(ExtractTextPlugin)会把整个项目中的代码打包到一起会影响页面的加载。所以我们要改进他的逻辑

    css分页面拆分

    这个很简单,只要在实例化ExtractTextPlugin插件的同时添加一个allchunks:true参数就可以了,还有就是把输出都指向一个css文件,用``[name]```区分chunk名

    const ExtractVueCss = new ExtractTextPlugin({filename:'styles/[name]-style.css',allChunks:true});
    

    按照我的习惯,先分出一份公共样式(root.css),其他的按chunk的数量进行分割,所以root.css的allchunks:false,其他的true

    const path = require('path')
    const webpack = require('webpack')
    const ExtractTextPlugin = require("extract-text-webpack-plugin")
    const ExtractRootCss = new ExtractTextPlugin({filename:'styles/[name]-root.css',allChunks:false});
    const ExtractVueCss = new ExtractTextPlugin({filename:'styles/[name]-style.css',allChunks:true});
    
    module.exports = {
        //other options...
        module:{
            rules:[
            //...
                {
                    test:/\.css$/,
                    //这里用的ExtractRootCss,输出到root.css
                    use:ExtractRootCss.extract({
                        fallback:'style-loader',
                        use:['css-loader']
                    })
                },
                {
                    test:/\.less$/,
                    //这里用的ExtractRootCss,输出到root.css
                    use:ExtractRootCss.extract({
                        fallback:'style-loader',
                        use:[
                            'css-loader',
                            'less-loader'
                        ]
                    })
                },
                {
                    test:/\.vue$/,
                    loader:'vue-loader',
                    options:{
                        loaders:{
                            //这里用的ExtractVueCss
                            'css': ExtractVueCss.extract({
                                use: 'css-loader',
                                fallback: 'vue-style-loader' 
                              }),
                            //这里用的ExtractVueCss
                            'less':
                            ExtractVueCss.extract({
                                use:[
                                    'css-loader',
                                    'less-loader'
                                ],
                                fallback:'vue-style-loader'
                            })
                        },
                    }
                },
            ]
        },
        plugins:[
            //填入插件实例,记得按顺序填入
            ExtractRootCss,//root.css
            ExtractVueCss,//vue内的css
            new webpack.HotModuleReplacementPlugin(),
        ]
    }
    

    多页面公共提取

    不懂commons-chunk-plugin,又不想看以前的文章的话看左边链接

    之前我们把node_modules内的模块抽取到了vender.js里面。

    //抽取从node_modules引入的模块,如vue,vue-router
    new webpack.optimize.CommonsChunkPlugin({
        name: 'vender',
        minChunks:function(module,count){
            var sPath = module.resource;
            // console.log(sPath,count);
            //匹配 node_modules文件目录
            return sPath &&
                /\.js$/.test(sPath) &&
                sPath.indexOf(
                    path.join(__dirname, 'node_modules')
                ) === 0
        }
    }),
    

    上上面设置了多个入口chunk,而且output的时候也不是设置成唯一的文件名,这样webpack打包的时候会自动按照入口chunk的数量生成相应数量的代码包,按目前情况会有app.js和home.js。

    但是问题在于每个chunk都会把所有他们用到的模块单独打包起来。比如app和home用到一样的header.vue模块,webpack都会分别打包到app.js和home.js里面。多次复用的模块最好是可以抽取出来,在首页加载过js文件之后得到了缓存,在详情页能马上得到提升页面加载速度。

    为了解决以上问题,我们再加一个公共代码提取的实例

    new webpack.optimize.CommonsChunkPlugin({
        name:'common'
        minChunks:2
    }),
    

    这样每个页面(入口chunk)中引入超过两次的模块就会打包到common.js文件下面。

    minChunks2代表所有chunk中复用超过2以上的模块会被提取。写成2粒度最小,你可以按自己需求修改。

    (5.20更新) 实际项目上发现,单一入口的时候,vendor.js并没有被抽离,原因可能是单一入口时common没被提前导致的(具体原因请大神指教),所以我们要再做一步入口数量判断

    Object.keys(config.page).length >= 2 
        ? new webpack.optimize.CommonsChunkPlugin({
                name: 'common',
                minChunks:2
            }):()=>{},
    
    抽取webpack的运行时逻辑

    参考vue-cli,我们会认为webpack的加载调度等运行时(runtime)逻辑是不会频繁修改的,所以我们把这部分抽离出来方面以后的页面都调用它。这样的话我们在加一个CommonsChunkPlugin实例,用于抽取这些逻辑。

    new webpack.optimize.CommonsChunkPlugin({
        name:'common'
        minChunks:2
    }),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'vender',
        minChunks:function(module,count){
            var sPath = module.resource;
            return sPath &&
                /\.js$/.test(sPath) &&
                sPath.indexOf(
                    path.join(__dirname, 'node_modules')
                ) === 0
        }
    }),
    //将webpack runtime 和一些复用部分抽取出来
    new webpack.optimize.CommonsChunkPlugin({
        name: 'manifest',
        minChunks:Infinity
    }),
    

    !注意!这里插件是有引入顺序的,顺序不对可能会导致操作被覆盖。

    minChunksInfinity什么chunk都不抽取出来,只抽取webpack的runtime等逻辑。

    抽取异步公共模块

    在开发vue或者react的时候可能会用到异步加载模块的能力(又叫懒加载),比如import()webpack require.ensure功能异步加载的模块。vue项目通常是vue-router懒加载组件的时候用到。

    虽然做多页开发的时候不一定会用到vue-router,但是我们让构建配置更健壮那就适配到spa的情况,把异步公共模块的提取也加进去

    // 放到上面三个CommonsChunkPlugin的最后
    
    new webpack.optimize.CommonsChunkPlugin({
        // names: ["app", "subPageA"]
        // (选择 chunks,或者忽略该项设置以选择全部 chunks)
        async: 'vendor-async',
        children: true,
        minChunks:2
    }),
    

    没有给name的话就会默认选择所有入口chunk。

    async:可以使true或者字符串,字符串的话就是生成的公共chunk的名字。(这个选项一直没搞懂,直到看到了这文章Webpack 大法之 Code Splitting

    children:选择所有被选 chunks 的子 chunks

    minChunks:大于等于两个chunk复用的子模块会提取到该公共chunk

    中文文档下面的示例有介绍 link

    改写生成的html模板

    同样,懒得看上期文章,又不懂HtmlWebpackPlugin的同学点这里

    HtmlWebpackPlugin插件在上一期中用到了,但是我们只是简单的设置了模板文件和出口文件。插件会默认把所有输出的chunk包和css文件都引入到生成的模板中。我们如下修改一下插件的配置

    plugins:[
            new HtmlWebpackPlugin({
                filename:'index.html',
                title:'vue demo',
                // favicon:'./src/images/logo.png',
                template:'./index.html',
                chunks:['app','vender','manifest','common'],
                chunksSortMode: 'dependency'
            }),
            new HtmlWebpackPlugin({
                filename:'home.html',
                title:'vue home',
                template:'./index.html',
                chunks:['home','vender','manifest','common'],
                chunksSortMode: 'dependency'
            }),
        ]
    

    filename:生成的文件名,区分页面起对应的名字

    template:html模板来源,应为是vue 项目所以用同样的模板,你可以按自己的需要来设置

    chunks:关键的来了,这个就是把本模板关联的chunks列举出来的参数,我把各种的入口chunk和提取出来的公共chunk填入了。

    chunksSortMode:chunk的引入顺序,'dependency'按依赖关系映入

    build一下

    运行npm run build,生成的dist文件目录结构如下

    dist目录

    并没有成产vendor-async.js异步公共模块是因为项目中还没有用到异步加载的部分

    到目前为止完整的webpack.config.js文件可以到这 下载

    ps

    Jason建议看别人的webpack配置时遇到不懂的插件多多查 npmjs 或者社区论坛,有时间看看去webpack官方介绍的插件或许里面有有你需要的

    Jason水平有限,如果有什么地方的知识点有错误请大家多多提点,在评论中告诉我。

    下期预告

    上一期的webpack配置完后完全可以应付单页应用的构建,而这一期还适应了多页面的构建需求,而且还学会了postcss和babelrc的基础配置方法。是不是感觉越来越接近vue-cli创建的项目了呢?

    引入了多页面的构建思想后,我们发现如果我们的页面不断的增加就要不断的给webpack.config.js添加插件和逻辑。所以为了方便和易用性,下一期我们来尝试写一些封装逻辑把构建配置封装起来,用一个文件整合常用的配置项统一对工程进行配置。

    下一期可能不会很快能更新,一方面因为手上有事情要忙,另一方面整项目我还没有封装测试好(这也正是为什么一直没有给出github的原因),所以请有关注本系列的同学可能要等等了,有需要的同学也可以关注我的账号留意更新。

    参考

    https://github.com/postcss/postcss/blob/HEAD/README.cn.md

    PostCSS配置指北.md

    PostCSS 是个什么鬼东西?

    html-webpack-plugin用法全解

    相关文章

      网友评论

        本文标题:从搭建vue-脚手架到掌握webpack配置(三.多页面构建)

        本文链接:https://www.haomeiwen.com/subject/utcjoxtx.html