4

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

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

这也可能是将 index.html 页面内的小徽标作为 base64 数据 URI 内联的一种有价值的方法。

该项目是使用 Angular CLI 创建的,并将 Angular 4 与 Webpack 2 一起使用。我使用 webpack.config.js 弹出了 Webpack 配置并对 webpack.config.js进行ng eject了少量修改。我几乎只是从配置中删除了 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
    }
};
4

2 回答 2

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。通过 css-loader 进一步加载 main.css。

为此,我创建了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 包含作为样式标签嵌入在 index.html 头部的 inline.css:

<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>
于 2017-04-12T12:46:00.117 回答
1

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

  <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>
于 2017-11-01T23:20:03.887 回答