首页 文章

使用Angular和Webpack强制编译CSS内联

提问于
浏览
3

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

目标是在Angular忙于引导时显示"Loading ..."页面的样式 . 为了避免FOUC,生成的CSS文件需要内联注入,因为我的index.html 's head. This way, once index.html is loaded, we don'中的样式标记需要等待加载另一个网络资源才能看到我们的预引导样式 .

这也可能是将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 回答

  • 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>
    
  • 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>
    

相关问题