Compiled CSS with Angular & Webpack

Is there a way to get Webpack to add the compiled SCSS -> CSS to the Angular project index.html as inline style tag

The goal is to style the Loading ... page while Angular is busy loading. To avoid FOUC, the resulting CSS file needs to be injected inline like a style tag in my head index.html. This way, once index.html is loaded, we don't have to wait for another network resource to load to see our preload style.

This can also be a decent approach for creating a small logo inside the index.html page as the database URI.

The project was built with Angular CLI and is using Angular 4 with Webpack 2. I selected the Webpack config with ng eject

and made some minor changes to webpack.config.js. I have almost completely removed the LESS and Stylus support from the configuration.

Here is my webpack.config.js for reference:

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
    }
};

      

+3


source to share


2 answers


I was able to solve my puzzle using a combination of extract-text-webpack-plugin and style-ext-html-webpack-plugin Let's assume a folder structure like this:

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

      

main.js contains the following:

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());

      

The goal is for Webpack to generate dist / index.html and render inline.css directly in the resulting index.html chapter. Next, main.css is loaded through the css loader.



For this, I created webpack.config.js like this:

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')
    ]
};

      

As a result, index.html contains inline.css embedded in the style tag in head.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


source


It was too much work, it's easier. This is the smallest minus you can do to make an animated counter without magic and additional plugins.



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

      

0


source







All Articles