From 503ca5db8bd65853914a99a0682e2adc6fdd8bfa Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Wed, 17 Mar 2021 13:41:01 +0100 Subject: [PATCH] Correctly inlining the global loader style --- loader/app/css/index.ts | 6 +- loader/app/css/loader.scss | 227 +++++++++++++++-------------- loader/app/css/overlay.scss | 100 ++++++------- package.json | 1 - webpack.config.ts | 4 +- webpack/HtmlWebpackInlineSource.ts | 138 ++++++++++++++++++ 6 files changed, 311 insertions(+), 165 deletions(-) create mode 100644 webpack/HtmlWebpackInlineSource.ts diff --git a/loader/app/css/index.ts b/loader/app/css/index.ts index 035afdf9..bfe4b056 100644 --- a/loader/app/css/index.ts +++ b/loader/app/css/index.ts @@ -1,3 +1,3 @@ -import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./index.scss"; -import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./loader.scss"; -import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./overlay.css"; \ No newline at end of file +import "./index.scss"; +import "./loader.scss"; +import "./overlay.css"; \ No newline at end of file diff --git a/loader/app/css/loader.scss b/loader/app/css/loader.scss index eda8d424..af9eabd8 100644 --- a/loader/app/css/loader.scss +++ b/loader/app/css/loader.scss @@ -1,138 +1,162 @@ -$setup-time-normal: 80s / 24; /* 24 frames / sec; the initial sequence is 80 frames */ -$setup-time-halloween: 323s / 24; -$loop-time-halloween: 25s / 24; +:global { + $setup-time-normal: 80s / 24; /* 24 frames / sec; the initial sequence is 80 frames */ + $setup-time-halloween: 323s / 24; + $loop-time-halloween: 25s / 24; -#loader-overlay { - position: absolute; - overflow: hidden; - - top: 0; - left: 0; - right: 0; - bottom: 0; - - background: #1e1e1e; - - user-select: none; - - z-index: 10000000; - - display: flex; - flex-direction: column; - justify-content: center; - - -webkit-app-region: drag; - - .container { - flex-shrink: 0; - - display: block; - position: relative; - - width: 1000px; - height: 1000px; - - align-self: center; - margin-bottom: 10vh; - - transition-duration: .5s; - - img { - user-select: none; - } - } - - .setup, .idle { + #loader-overlay { position: absolute; + overflow: hidden; top: 0; left: 0; right: 0; bottom: 0; - display: none; + background: #1e1e1e; + + user-select: none; + + z-index: 10000000; + + display: flex; + flex-direction: column; + justify-content: center; + + -webkit-app-region: drag; + + .container { + flex-shrink: 0; - &.visible { display: block; - } - } + position: relative; - .setup.visible { - &.normal { - animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time-normal forwards; + width: 1000px; + height: 1000px; + + align-self: center; + margin-bottom: 10vh; + + transition-duration: .5s; + + img { + user-select: none; + } } - &.halloween { - animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time-halloween forwards; - } - } - - .idle.animation-normal { - img { - position: absolute; - } - - .steam { + .setup, .idle { position: absolute; - top: 282px; - left: 380px; + top: 0; + left: 0; + right: 0; + bottom: 0; - width: 249px; - height: 125px; - background: url("../images/steam.png") 0 0; + display: none; - animation: sprite-steam 2.5s steps(50) forwards infinite; + &.visible { + display: block; + } } - } - &.finishing { - .idle { + .setup.visible { + &.normal { + animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time-normal forwards; + } + + &.halloween { + animation: loader-initial-sequence 0s cubic-bezier(.81,.01,.65,1.16) $setup-time-halloween forwards; + } + } + + .idle.animation-normal { + img { + position: absolute; + } + .steam { - display: none; - } + position: absolute; - .bowl { - animation: swipe-out-bowl .5s both; - } + top: 282px; + left: 380px; - .text { - animation: swipe-out-text .5s .12s both; + width: 249px; + height: 125px; + background: url("../images/steam.png") 0 0; + + animation: sprite-steam 2.5s steps(50) forwards infinite; } } - pointer-events: none; - animation: overlay-fade .3s .2s both; + &.finishing { + .idle { + .steam { + display: none; + } + + .bowl { + animation: swipe-out-bowl .5s both; + } + + .text { + animation: swipe-out-text .5s .12s both; + } + } + + pointer-events: none; + animation: overlay-fade .3s .2s both; + } + + .loader-stage { + position: absolute; + + left: 5px; + bottom: 5px; + + font-size: 12px; + font-family: monospace; + + color: #999; + } } - .loader-stage { - position: absolute; + /* Automated loader timeout */ + #loader-overlay:not(.initialized) + #critical-load:not(.shown) { + display: block !important; + opacity: 0; - left: 5px; - bottom: 5px; + animation: loader-setup-timeout 0s ease-in $setup-time-normal forwards; - font-size: 12px; - font-family: monospace; + .error::before { + content: 'Failed to startup loader!'; + } - color: #999; + .detail::before { + content: 'Lookup the console for more details'; + } } } @media all and (max-width: 850px) { - #loader-overlay .container { - transform: scale(.5); + :global { + #loader-overlay .container { + transform: scale(.5); + } } } @media all and (max-height: 700px) { - #loader-overlay .container { - transform: scale(.5); + :global { + #loader-overlay .container { + transform: scale(.5); + } } } @media all and (max-width: 400px) { - #loader-overlay .container { - transform: scale(.3); + :global { + #loader-overlay .container { + transform: scale(.3); + } } } @@ -192,23 +216,6 @@ $loop-time-halloween: 25s / 24; } } -/* Automated loader timeout */ -#loader-overlay:not(.initialized) + #critical-load:not(.shown) { - display: block !important; - opacity: 0; - - animation: loader-setup-timeout 0s ease-in $setup-time-normal forwards; - - .error::before { - content: 'Failed to startup loader!'; - } - - .detail::before { - content: 'Lookup the console for more details'; - } -} - - @keyframes loader-setup-timeout { to { opacity: 1; diff --git a/loader/app/css/overlay.scss b/loader/app/css/overlay.scss index 3e76ba17..d285c96d 100644 --- a/loader/app/css/overlay.scss +++ b/loader/app/css/overlay.scss @@ -1,64 +1,66 @@ -#overlay-no-js, #critical-load { - z-index: 100000000; - display: none; - position: fixed; +:global { + #overlay-no-js, #critical-load { + z-index: 100000000; + display: none; + position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; - background: #1e1e1e; - text-align: center; + background: #1e1e1e; + text-align: center; - -webkit-app-region: drag; + -webkit-app-region: drag; - h1, h3, a { - -webkit-app-region: no-drag; - } + h1, h3, a { + -webkit-app-region: no-drag; + } - .container { - position: relative; - display: inline-block; + .container { + position: relative; + display: inline-block; - top: 20%; - } + top: 20%; + } - &.shown { - display: block; - } -} - -#critical-load { - img { - height: 12em - } - - .error { - color: #bd1515; - margin-bottom: 0 - } - - .detail { - color: #696363; - margin-top: .5em - } -} - - -@media (max-height: 750px) { - #critical-load .container { - top: unset; + &.shown { + display: block; + } } #critical-load { - font-size: .8rem; + img { + height: 12em + } - flex-direction: column; - justify-content: center; + .error { + color: #bd1515; + margin-bottom: 0 + } + + .detail { + color: #696363; + margin-top: .5em + } } - #critical-load.shown { - display: flex; + + @media (max-height: 750px) { + #critical-load .container { + top: unset; + } + + #critical-load { + font-size: .8rem; + + flex-direction: column; + justify-content: center; + } + + #critical-load.shown { + display: flex; + } } } \ No newline at end of file diff --git a/package.json b/package.json index d49e298e..d0282a73 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "node-sass": "^4.14.1", "potpack": "^1.0.1", "raw-loader": "^4.0.0", - "react-dev-utils": "^11.0.4", "sass": "1.22.10", "sass-loader": "^8.0.2", "sha256": "^0.2.0", diff --git a/webpack.config.ts b/webpack.config.ts index 29260b19..83654405 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -11,7 +11,7 @@ const webpack = require("webpack"); import { Plugin as SvgSpriteGenerator } from "webpack-svg-sprite-generator"; const ManifestGenerator = require("./webpack/ManifestPlugin"); -const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin"); +const HtmlWebpackInlineSourcePlugin = require("./webpack/HtmlWebpackInlineSource"); import HtmlWebpackPlugin from "html-webpack-plugin"; const MiniCssExtractPlugin = require('mini-css-extract-plugin'); @@ -155,7 +155,7 @@ export const config = async (target: "web" | "client"): Promise !!e), module: { diff --git a/webpack/HtmlWebpackInlineSource.ts b/webpack/HtmlWebpackInlineSource.ts new file mode 100644 index 00000000..d25c705f --- /dev/null +++ b/webpack/HtmlWebpackInlineSource.ts @@ -0,0 +1,138 @@ +const escapeRegex = require('escape-string-regexp'); +const path = require('path'); +const slash = require('slash'); +const sourceMapUrl = require('source-map-url'); + +function HtmlWebpackInlineSourcePlugin (htmlWebpackPlugin) { + this.htmlWebpackPlugin = htmlWebpackPlugin; +} + +HtmlWebpackInlineSourcePlugin.prototype.apply = function (compiler) { + const self = this; + + // Hook into the html-webpack-plugin processing + compiler.hooks.compilation.tap('html-webpack-inline-source-plugin', compilation => { + self.htmlWebpackPlugin + .getHooks(compilation) + .alterAssetTagGroups.tapAsync('html-webpack-inline-source-plugin', (htmlPluginData, callback) => { + if (!htmlPluginData.plugin.options.inlineSource) { + return callback(null, htmlPluginData); + } + + const regexStr = htmlPluginData.plugin.options.inlineSource; + const result = self.processTags(compilation, regexStr, htmlPluginData); + callback(null, result); + }); + }); +}; + +HtmlWebpackInlineSourcePlugin.prototype.processTags = function (compilation, regexStr, pluginData) { + const self = this; + + const bodyTags = []; + const headTags = []; + + const regex = new RegExp(regexStr); + const filename = pluginData.plugin.options.filename; + + pluginData.headTags.forEach(function (tag) { + headTags.push(self.processTag(compilation, regex, tag, filename)); + }); + + pluginData.bodyTags.forEach(function (tag) { + bodyTags.push(self.processTag(compilation, regex, tag, filename)); + }); + + return { headTags: headTags, bodyTags: bodyTags, plugin: pluginData.plugin, outputName: pluginData.outputName }; +}; + +HtmlWebpackInlineSourcePlugin.prototype.resolveSourceMaps = function (compilation, assetName, asset) { + let source = asset.source(); + const out = compilation.outputOptions; + // Get asset file absolute path + const assetPath = path.join(out.path, assetName); + // Extract original sourcemap URL from source string + if (typeof source !== 'string') { + source = source.toString(); + } + const mapUrlOriginal = sourceMapUrl.getFrom(source); + // Return unmodified source if map is unspecified, URL-encoded, or already relative to site root + if (!mapUrlOriginal || mapUrlOriginal.indexOf('data:') === 0 || mapUrlOriginal.indexOf('/') === 0) { + return source; + } + // Figure out sourcemap file path *relative to the asset file path* + const assetDir = path.dirname(assetPath); + const mapPath = path.join(assetDir, mapUrlOriginal); + const mapPathRelative = path.relative(out.path, mapPath); + // Starting with Node 6, `path` module throws on `undefined` + const publicPath = out.publicPath || ''; + // Prepend Webpack public URL path to source map relative path + // Calling `slash` converts Windows backslashes to forward slashes + const mapUrlCorrected = slash(path.join(publicPath, mapPathRelative)); + // Regex: exact original sourcemap URL, possibly '*/' (for CSS), then EOF, ignoring whitespace + const regex = new RegExp(escapeRegex(mapUrlOriginal) + '(\\s*(?:\\*/)?\\s*$)'); + // Replace sourcemap URL and (if necessary) preserve closing '*/' and whitespace + return source.replace(regex, function (match, group) { + return mapUrlCorrected + group; + }); +}; + +HtmlWebpackInlineSourcePlugin.prototype.processTag = function (compilation, regex, tag, filename) { + let assetUrl; + let preTag = tag; + + // inline js + if (tag.tagName === 'script' && tag.attributes && regex.test(tag.attributes.src)) { + assetUrl = tag.attributes.src; + tag = { + tagName: 'script', + closeTag: true, + attributes: { + type: 'text/javascript' + } + }; + + // inline css + } else if (tag.tagName === 'link' && regex.test(tag.attributes.href)) { + assetUrl = tag.attributes.href; + tag = { + tagName: 'style', + closeTag: true, + attributes: { + type: 'text/css' + } + }; + } + + if (assetUrl) { + // Strip public URL prefix from asset URL to get Webpack asset name + const publicUrlPrefix = compilation.outputOptions.publicPath || ''; + // if filename is in subfolder, assetUrl should be prepended folder path + if (path.basename(filename) !== filename) { + assetUrl = path.dirname(filename) + '/' + assetUrl; + } + const assetName = path.posix.relative(publicUrlPrefix, assetUrl); + const asset = getAssetByName(compilation.assets, assetName); + if (compilation.assets[assetName] !== undefined) { + const updatedSource = this.resolveSourceMaps(compilation, assetName, asset); + tag.innerHTML = (tag.tagName === 'script') ? updatedSource.replace(/(<)(\/script>)/g, '\\x3C$2') : updatedSource; + }else{ + return preTag; + } + } + + return tag; +}; + +function getAssetByName (assests, assetName) { + for (let key in assests) { + if (assests.hasOwnProperty(key)) { + let processedKey = path.posix.relative('', key); + if (processedKey === assetName) { + return assests[key]; + } + } + } +} + +module.exports = HtmlWebpackInlineSourcePlugin; \ No newline at end of file