使用Angular&强制编译CSS内联的WebPack

时间:2017-04-11 12:44:02

标签: angular webpack webpack-2 html-webpack-plugin extract-text-plugin

有没有办法让Webpack将已编译的 SCSS→CSS 文件添加到我的Angular项目的 index.html 头部作为内联样式标记?

目标是在Angular忙于引导时显示" Loading ..." 页面的样式。为了避免FOUC,生成的CSS文件需要内联注入,作为我的index.html头文件中的样式标记。这样,一旦加载了index.html,我们就不需要等待另一个网络资源加载,以便查看我们的预引导样式。

这可能是一种值得的方法,可以将index.html页面中的小徽标内嵌为base64数据URI。

该项目是使用Angular CLI创建的,并将Angular 4与Webpack 2一起使用。我使用ng eject弹出了Webpack配置,并对 webpack.config.js 进行了少量修改。我几乎只从配置中删除了LESS和Stylus支持。

这是我的webpack.config.js供参考:

const path = require('path');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const ProvidePlugin = require('webpack/lib/ProvidePlugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const autoprefixer = require('autoprefixer');
const postcssUrl = require('postcss-url');

const {NoEmitOnErrorsPlugin, LoaderOptionsPlugin} = require('webpack');
const {GlobCopyWebpackPlugin, BaseHrefWebpackPlugin} = require('@angular/cli/plugins/webpack');
const {CommonsChunkPlugin} = require('webpack').optimize;
const {AotPlugin} = require('@ngtools/webpack');

const nodeModules = path.join(process.cwd(), 'node_modules');
const entryPoints = ["inline", "polyfills", "sw-register", "styles", "twbs", "vendor", "main"];
const baseHref = "";
const deployUrl = "";

module.exports = {
    devtool: "source-map",
    devServer: {
        port: 4200,
        host: "0.0.0.0",
        historyApiFallback: true
    },
    resolve: {
        extensions: [
            ".ts",
            ".js"
        ],
        modules: [
            "./node_modules"
        ]
    },
    resolveLoader: {
        modules: [
            "./node_modules"
        ]
    },
    entry: {
        main: [
            "./src/main.ts"
        ],
        polyfills: [
            "./src/polyfills.ts"
        ],
        styles: [
            "./src/styles/styles.scss",
            "./src/styles/vendor.scss"
        ],
        twbs: 'bootstrap-loader'
    },
    output: {
        path: path.join(process.cwd(), "dist"),
        filename: "[name].bundle.js",
        chunkFilename: "[id].chunk.js"
    },
    module: {
        rules: [
            {
                enforce: "pre",
                test: /\.js$/,
                loader: "source-map-loader",
                exclude: [
                    /node_modules/
                ]
            },
            {
                test: /\.json$/,
                loader: "json-loader"
            },
            {
                test: /\.html$/,
                loader: "raw-loader"
            },
            {
                test: /\.(eot|svg)$/,
                loader: "file-loader?name=[name].[hash:20].[ext]"
            },
            {
                test: /\.(jpg|png|gif|otf|ttf|woff|woff2|cur|ani)$/,
                loader: "url-loader?name=[name].[hash:20].[ext]&limit=10000"
            },
            {
                exclude: [
                    path.join(process.cwd(), "src/styles/styles.scss"),
                    path.join(process.cwd(), "src/styles/vendor.scss")
                ],
                test: /\.css$/,
                loaders: [
                    "exports-loader?module.exports.toString()",
                    "css-loader?{\"sourceMap\":false,\"importLoaders\":1}",
                    "postcss-loader"
                ]
            },
            {
                exclude: [
                    path.join(process.cwd(), "src/styles/styles.scss"),
                    path.join(process.cwd(), "src/styles/vendor.scss")
                ],
                test: /\.scss$/,
                loaders: [
                    "exports-loader?module.exports.toString()",
                    "css-loader?{\"sourceMap\":false,\"importLoaders\":1}",
                    "postcss-loader",
                    "sass-loader"
                ]
            },
            {
                include: [
                    path.join(process.cwd(), "src/styles/styles.scss"),
                    path.join(process.cwd(), "src/styles/vendor.scss")
                ],
                test: /\.css$/,
                loaders: ExtractTextPlugin.extract({
                    use: [
                        "css-loader?{\"sourceMap\":false,\"importLoaders\":1}",
                        "postcss-loader"
                    ],
                    fallback: "style-loader",
                    publicPath: ""
                })
            },
            {
                include: [
                    path.join(process.cwd(), "src/styles/styles.scss"),
                    path.join(process.cwd(), "src/styles/vendor.scss")
                ],
                test: /\.scss$/,
                loaders: ExtractTextPlugin.extract({
                    use: [
                        "css-loader?{\"sourceMap\":false,\"importLoaders\":1}",
                        "postcss-loader",
                        "sass-loader"
                    ],
                    fallback: "style-loader",
                    publicPath: ""
                })
            },
            {
                test: /\.ts$/,
                loader: "@ngtools/webpack"
            }
        ]
    },
    plugins: [
        new ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "window.jQuery": "jquery",
            Tether: "tether",
            "window.Tether": "tether",
            Tooltip: "exports-loader?Tooltip!bootstrap/js/dist/tooltip",
            Alert: "exports-loader?Alert!bootstrap/js/dist/alert",
            Button: "exports-loader?Button!bootstrap/js/dist/button",
            Carousel: "exports-loader?Carousel!bootstrap/js/dist/carousel",
            Collapse: "exports-loader?Collapse!bootstrap/js/dist/collapse",
            Dropdown: "exports-loader?Dropdown!bootstrap/js/dist/dropdown",
            Modal: "exports-loader?Modal!bootstrap/js/dist/modal",
            Popover: "exports-loader?Popover!bootstrap/js/dist/popover",
            Scrollspy: "exports-loader?Scrollspy!bootstrap/js/dist/scrollspy",
            Tab: "exports-loader?Tab!bootstrap/js/dist/tab",
            Util: "exports-loader?Util!bootstrap/js/dist/util"
        }),
        new NoEmitOnErrorsPlugin(),
        new GlobCopyWebpackPlugin({
            patterns: [
                "assets",
                "favicon.ico"
            ],
            globOptions: {
                "cwd": "./src",
                "dot": true,
                "ignore": "**/.gitkeep"
            }
        }),
        new ProgressPlugin(),
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            filename: "./index.html",
            hash: false,
            inject: true,
            compile: true,
            favicon: false,
            minify: false,
            cache: true,
            showErrors: true,
            chunks: "all",
            excludeChunks: [],
            title: "Webpack App",
            xhtml: true,
            chunksSortMode: function sort(left, right) {
                let leftIndex = entryPoints.indexOf(left.names[0]);
                let rightindex = entryPoints.indexOf(right.names[0]);
                if (leftIndex > rightindex) {
                    return 1;
                }
                else if (leftIndex < rightindex) {
                    return -1;
                }
                else {
                    return 0;
                }
            }
        }),
        new BaseHrefWebpackPlugin({}),
        new CommonsChunkPlugin({
            name: "inline",
            minChunks: null
        }),
        new CommonsChunkPlugin({
            name: "vendor",
            minChunks: (module) => module.resource && module.resource.startsWith(nodeModules),
            chunks: [
                "main"
            ]
        }),
        new ExtractTextPlugin({
            filename: "[name].bundle.css",
            disable: true
        }),
        new LoaderOptionsPlugin({
            sourceMap: false,
            options: {
                postcss: [
                    autoprefixer(),
                    postcssUrl({
                        url: (URL) => {
                            // Only convert root relative URLs, which CSS-Loader won't process into require().
                            if (!URL.startsWith('/') || URL.startsWith('//')) {
                                return URL;
                            }
                            if (deployUrl.match(/:\/\//)) {
                                // If deployUrl contains a scheme, ignore baseHref use deployUrl as is.
                                return `${deployUrl.replace(/\/$/, '')}${URL}`;
                            }
                            else if (baseHref.match(/:\/\//)) {
                                // If baseHref contains a scheme, include it as is.
                                return baseHref.replace(/\/$/, '') +
                                    `/${deployUrl}/${URL}`.replace(/\/\/+/g, '/');
                            }
                            else {
                                // Join together base-href, deploy-url and the original URL.
                                // Also dedupe multiple slashes into single ones.
                                return `/${baseHref}/${deployUrl}/${URL}`.replace(/\/\/+/g, '/');
                            }
                        }
                    })
                ],
                sassLoader: {
                    sourceMap: false,
                    includePaths: []
                },
                context: ""
            }
        }),
        new AotPlugin({
            mainPath: "main.ts",
            hostReplacementPaths: {
                "environments/environment.ts": "environments/environment.ts"
            },
            exclude: [],
            tsConfigPath: "src/tsconfig.app.json",
            skipCodeGeneration: true
        })
    ],
    node: {
        fs: "empty",
        global: true,
        crypto: "empty",
        tls: "empty",
        net: "empty",
        process: true,
        module: false,
        clearImmediate: false,
        setImmediate: false
    }
};

2 个答案:

答案 0 :(得分:1)

我设法使用extract-text-webpack-pluginstyle-ext-html-webpack-plugin的组合来解决我的谜题。让我们假设一个文件夹结构如下:

|- src
   |- index.ejs
   |- inline.css
   |- main.css
   |- main.js

main.js 包含以下内容:

import _ from 'lodash';
import './inline.css';
import './main.css';

function component() {
    const element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'Webpack', '!!!'], ' ');
    return element;
}

document.body.appendChild(component());

目标是让Webpack生成dist / index.html并直接在index.html的结果头中渲染inline.css。进一步的main.css通过css-loader加载。

为实现这一目标,我按如下方式创建了 webpack.config.js

const path = require('path');

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const StyleExtHtmlPlugin = require('style-ext-html-webpack-plugin');

const extractSplashCSS = new ExtractTextPlugin('splash.css');
const extractMainCSS = new ExtractTextPlugin('main.css');

module.exports = {
    entry: {
        main: './src/main.js'
    },
    output: {
        path: path.join(process.cwd(), 'dist'),
        filename: '[name].bundle.js'
    },
    module: {
        rules: [
            {
                include: [
                    path.join(process.cwd(), 'src/inline.css')
                ],
                test: /\.css$/,
                loaders: extractSplashCSS.extract({
                    use: 'css-loader'
                })
            },
            {
                exclude: [
                    path.join(process.cwd(), 'src/inline.css')
                ],
                test: /\.css$/,
                loaders: extractMainCSS.extract({
                    use: 'css-loader'
                })
            }
        ]
    },
    plugins: [
        extractSplashCSS,
        extractMainCSS,
        new HtmlWebpackPlugin({
            title: 'Hello Webpack 2',
            template: 'src/index.ejs',
            filename: 'index.html'
        }),
        new StyleExtHtmlPlugin('splash.css')
    ]
};

生成的index.html包含inline.css作为样式标记嵌入index.html的头部:

<html>
<head>
<title>Hello Webpack 2</title>
<style>body {
    background-color: lightgrey;
}</style><link href="main.css" rel="stylesheet"></head>
<body>
<p>Webpack 2...</p>
<script type="text/javascript" src="main.bundle.js"></script></body>
</html>

答案 1 :(得分:0)

这是太多的工作,这更容易。这是最简单的你可以做一个没有魔法和额外插件的动画微调器。

  <app-root>
  <style type="text/css">
    initial-loading-indicator {
      z-index: 1200;
      position: fixed;
      top: 50%;
      left: 50%;
      content: '';
      font-size: 10px;
      width: 1em;
      height: 1em;
      -ms-animation: spinner 1500ms infinite linear;
      animation: spinner 1500ms infinite linear;
      border-radius: 0.5em;
      box-shadow: #495057 1.5em 0 0 0, #495057 1.1em 1.1em 0 0, #495057 0 1.5em 0 0, #495057 -1.1em 1.1em 0 0, #495057 -1.5em 0 0 0, #495057 -1.1em -1.1em 0 0, #495057 0 -1.5em 0 0, #495057 1.1em -1.1em 0 0;
    }

    @keyframes spinner {
      0% {
        -webkit-transform: rotate(0deg);
        -ms-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
        transform: rotate(360deg);
      }
    }
  </style>
<initial-loading-indicator></initial-loading-indicator>
  </app-root>