有没有办法让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
}
};
答案 0 :(得分:1)
我设法使用extract-text-webpack-plugin和style-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>