Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpack如何extract多个css #5

Open
shirleyMHao opened this issue Jul 10, 2018 · 2 comments
Open

webpack如何extract多个css #5

shirleyMHao opened this issue Jul 10, 2018 · 2 comments
Labels

Comments

@shirleyMHao
Copy link
Owner

shirleyMHao commented Jul 10, 2018

用webpack打包css的你是不是一直借助exact-text-webpack-plugin来生成外部样式文件?
对于多页应用,不同的页面也总会用到公用的组件,如何将组件样式分离出来,更有利于缓存?
你是否在想,css我到底是打包成一个好,还是多个好?这个问题的答案是”看情况“。具体大家可以参考资源合并好还是拆分好.

那么,今儿咱们抛开资源合并和拆分的分歧, 来聊一聊webpack 打包如何生成多个external css.

首先来看我们的目录结构:

---common
|    |---css
|    |    |---normalize.scss
---components
|    |---header
|    |    |---header.js
|    |    |---header.css
|    |---footer
|    |    |---footer.js
|    |    |---footer.css
---view
|    |---css
|    |    |---main.css
|    |---js
|    |     |---main.js
|    |---index.html
---webpack.config.js 

其中view/js/main.js的代码如下:

    import header from '@/components/header/header.js';
    import footer from '@/components/footer/footer.js';
    import '../css/main.css';
    import '../css/normalize.scss';

header.js代码:

    import './header.css';

footer.js代码与header.js类似。

说明:组件的js引用组件相关的样式,应该是比较符合大家编程习惯的。

初始的webpack.config.js配置如下:

const path = require('path');
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
assetsPath = function (_path) {
  var assetsSubDirectory = 'static'
  return path.posix.join(assetsSubDirectory, _path)
}
module.export= {
    entry: {
        main: './view/js/main.js'
    },
    output: {
        filename: assetsPath('src/main.[hash:7].js'),
        path: path.resolve(__dirname, 'dist')
    },
    resolve: {
        extensions: ['.js', '.json'],
        alias: {
            '@': path.resolve(__dirname),
        },
        symlinks: false
    },
    module: {
        rules: [
            ...,
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    fallback: "style-loader",
                    use: "css-loader"
                })
            }
        ],
    }
    plugins: [
        //html-webpack-plugin
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: './view/index.html',
            inject: true,
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true
            }
        }),
        new ExtractTextPlugin({
            filename: assetsPath('css/[name].[contenthash].css')
        })
    ]
}

注: 已上代码省略了与本文所讲无关的配置,比如babel-loader、sass-loader等

基于以上这种webpack的配置方式, 可以将main.css、normalize.css、以及组件的header.css和footer.css都打包为一个main.[contenthash].css。
如果我们希望把组件的样式、normalize都单独打包出来,最容易被想到的方案是添加一个entry,这样就可以多打包一个css出来了。我们详细的来看一下这个方案。

方案一

思路: 添加一个entry, 专门用于 import 组件的样式。 在view/js目录下增加一个app.js。app.js的代码如下:

import '@/components/header/header.css';
import '@/components/footer/footer.css';

这样一来,为防止这两个组件样式重复打包进main.[contenthash].css, 我们在@/components/header/header.js 和@/components/footer/footer.js 中都不能再重复的引用相应的css了(header.css 和 footer.css),也就是组件所需的样式不能够在组件的js中引用。
这种方式是不是不太符合你的编程习惯呢? 每次增加一个组件,也需要单独在app.js中再引入样式,真实太奇怪了
基于这两点 方案一 被 pass了。

方案二

然后经过查看extract-text-webpack-plugin的文档,发现官方其实提供了 Multiple Instance 的方案。
multiple

我们就来充分利用这个特性,同时利用webpack 的 Rule.includeRule.exclue特性来做为我们的方案二。
有改动的部分代码如下:

//用于extract main.css
var extractMain = new ExtractTextPlugin('css/[name].[contenthash].css');
//用于extract 
var esxtractCommon = new ExtractTextPlugin('css/[name]-component.[contenthash].css');

export.module = {
    ...,
    module: {
        rules: [
            ...,
            {
                test: /\.css$/,
                include: [path.resolve(__dirname, './components')]
                use: esxtractCommon.extract({
                    fallback: "style-loader",
                    use: "css-loader"
                })
            },
            {
                test: /\.css$/,
                exclude: [path.resolve(__dirname, './components')]
                use: extractMain.extract({
                    fallback: "style-loader",
                    use: "css-loader"
                })
            }
        ]
    },
    plugins: [
        extractMain,
        esxtractCommon
    ]
}

到这里我们就将components中的css都抽取到[name]-component.[contenthash].css中了,main.css还是放在[name].[contenthash].css中

看到这儿,你有什么疑惑吗?停顿5s,想一下
Welcome back!是的,我们最开始的时候承诺的将normalize.css 也单独抽取出来还没有实现。
这个实现方式就比较简单了。我们将scss 做为一个入口(是的,你没看错,是scss)。那么config就变成了下面这个样子:

entry:{
    main: './view/js/main.js',
    normal: './common/css/normalize.scss'
}

多个css顺序问题

CSS 是 Cascading Style Sheets 的简称,其层叠特性想必大家一定非常清楚了。因此现在有三个inject的css,我们要保持顺序的插入。我们期望的顺序是 :normalize.scss编译出来的normal.css放在第一个。

这个顺序,我们用html-webpack-pluginchunksSortMode来控制

plugins: [
        //html-webpack-plugin
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: './view/index.html',
            inject: true,
            chunks: ['normal','main']
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true
            },
            chunksSortMode: function(chunk1,chunk2){
                //orders 就是我们期望的顺序
                var orders = ['normal','main'];
                var chunk1Index = orders.indexOf(chunk1.names[0]);
                var chunk2Index = orders.indexOf(chunk2.names[0]);

                if (chunk1Index > chunk2Index) {
                    return 1;
                } else if (chunk1Index < chunk2Index) {
                    return -1;
                } else {
                    return 0;
                }
            },
            excludeAssets: [/normal.js/]
        }),
        new HtmlWebpackExcludeAssetsPlugin()
    ]

对于我们新引入的plugin HtmlWebpackExcludeAssetsPlugin, 是因为加入了一个scss entry,必然会生成一个normal.js, 这个js就好像一个无用的附属品,因此打包的时候我们利用这个插件把它排除出去了。

至此,我们进行了完成的css extract,并对css的inject顺序做了干预。

如果有问题,大家可以再issue 一起讨论,欢迎赐教。

@yiludege
Copy link

棒,不过现在ExtractTextPlugin又废弃了,改用mini-css-extract-plugin

@kuangbeibei
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants