Skip to content

Commit

Permalink
feat: add support the ?inline query for styles imported in JavaScript
Browse files Browse the repository at this point in the history
  • Loading branch information
webdiscus committed Jul 24, 2024
1 parent 361ff6c commit 19eb206
Show file tree
Hide file tree
Showing 14 changed files with 206 additions and 64 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Change log

## 3.17.0 (2024-07-23)

- feat: add support the `?inline` query for styles imported in JavaScript:
```js
import './style-a.css?inline'; // the extracted CSS will be injected into HTML
import './style-b.css'; // the extracted CSS will be saved into separate output file
```

## 3.16.0 (2024-07-23)

- feat: add `runtime` option for the `handlebars` preprocessor
Expand Down
21 changes: 12 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "html-bundler-webpack-plugin",
"version": "3.16.0",
"version": "3.17.0",
"description": "HTML bundler plugin for webpack handles a template as an entry point, extracts CSS and JS from their sources referenced in HTML, supports template engines like Eta, EJS, Handlebars, Nunjucks.",
"keywords": [
"html",
Expand Down Expand Up @@ -177,7 +177,7 @@
"nunjucks": "^3.2.4",
"parse5": "^7.1.2",
"postcss-loader": "^8.1.1",
"prettier": "^3.3.2",
"prettier": "^3.3.3",
"prismjs": "^1.29.0",
"pug": "^3.0.3",
"react": "18.3.1",
Expand Down
136 changes: 93 additions & 43 deletions src/Plugin/AssetCompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,6 @@ class AssetCompiler {
const { createHash } = this.webpack.util;
const isAutoPublicPath = Option.isAutoPublicPath();
const publicPath = Option.getPublicPath();
const inline = Option.getCss().inline;
const esModule = Collection.isImportStyleEsModule();
const urlRegex = new RegExp(`${esModule ? baseUri : ''}${urlPathPrefix}(.+?)(?=\\))`, 'g');
const entry = this.currentEntryPoint;
Expand All @@ -1056,19 +1055,24 @@ class AssetCompiler {
const sources = [];
const resources = [];
const imports = [];
const inlineSources = [];
const inlineResources = [];
const inlineImports = [];
let cssHash = '';

// 1. get styles from all nested files imported in the root JS file and sort them
const modules = Collection.findImportedModules(entry.id, issuer, chunk);

// 2. squash styles from all nested files into one file
// 2. squash styles from all nested files and group by inline/file type
const uniqueModuleIds = new Set();
for (const { module } of modules) {
if (uniqueModuleIds.has(module.debugId)) {
continue;
}

const isUrl = module.resourceResolveData?.query.includes('url');
const urlQuery = module.resourceResolveData?.query || '';
const isUrl = urlQuery.includes('url');
const isInline = Option.isInlineCss(urlQuery);
const importData = {
resource: module.resource,
assets: [],
Expand All @@ -1078,15 +1082,15 @@ class AssetCompiler {
const { assetsInfo } = module.buildInfo;

if (assetsInfo) {
for (const [assetFile2, asset] of assetsInfo) {
for (const [assetFile, asset] of assetsInfo) {
const sourceFilename = asset.sourceFilename;
const stylePath = path.dirname(module.resource);

const data = {
type: Collection.type.resource,
inline: false,
inline: isInline,
resource: path.resolve(stylePath, sourceFilename),
assetFile: assetFile2,
assetFile: assetFile,
issuer: {
resource: module.resource,
},
Expand Down Expand Up @@ -1114,20 +1118,26 @@ class AssetCompiler {
}

cssHash += module.buildInfo.hash;
sources.push(...module._cssSource);
imports.push(importData);
resources.push(module.resource);
uniqueModuleIds.add(module.debugId);

if (isInline) {
inlineSources.push(...module._cssSource);
inlineResources.push(module.resource);
inlineImports.push(importData);
} else {
sources.push(...module._cssSource);
resources.push(module.resource);
imports.push(importData);
}
}

if (sources.length === 0) continue;
if (sources.length === 0 && inlineSources.length === 0) continue;

// 3. generate output filename

// mixin importStyleIdx into hash to generate new hash after changes
cssHash += Collection.importStyleIdx++;

//const hash = this.webpack.util.createHash('md4').update(sources.toString()).digest('hex');
const hash = createHash('md4').update(cssHash).digest('hex');
const { isCached, filename } = this.getStyleAsseFile({
name: issuerEntry.name,
Expand All @@ -1137,45 +1147,85 @@ class AssetCompiler {
useChunkFilename: true,
});

const assetFile = inline ? this.getInlineStyleAsseFile(filename, entryFilename) : filename;
const outputFilename = inline ? assetFile : Option.getAssetOutputFile(assetFile, entryFilename);

Collection.setData(
entry,
{ resource: issuer },
{
type: Collection.type.style,
inline,
imported: true,
// if style is imported then resource is the array of imported source files
resource: resources,
assetFile: outputFilename,
imports,
}
);
// CSS injected into HTML
if (inlineSources.length) {
const assetFile = this.getInlineStyleAsseFile(filename, entryFilename);
const outputFilename = assetFile;

Collection.setData(
entry,
{ resource: issuer },
{
type: Collection.type.style,
inline: true,
imported: true,
// if style is imported then resource is the array of imported source files
resource: inlineResources,
assetFile: outputFilename,
imports: inlineImports,
}
);

// skip already processed styles except inlined
if (isCached && !inline) {
continue;
// 4. extracts CSS content from squashed sources
const issuerFilename = entryFilename;

const resolveAssetFile = (match, file) =>
isAutoPublicPath ? Option.getAssetOutputFile(file, issuerFilename) : path.posix.join(publicPath, file);

const cssContent = CssExtractModule.apply(inlineSources, (content) =>
content.replace(urlRegex, resolveAssetFile)
);

// 5. add extracted CSS file into compilation
const fileManifest = {
render: () => cssContent,
filename: assetFile,
identifier: `${pluginName}.${chunk.id}`,
hash,
};

result.push(fileManifest);
}

// 4. extracts CSS content from squashed sources
const issuerFilename = inline ? entryFilename : assetFile;
// CSS saved into file
if (sources.length) {
const assetFile = filename;
const outputFilename = Option.getAssetOutputFile(assetFile, entryFilename);

Collection.setData(
entry,
{ resource: issuer },
{
type: Collection.type.style,
inline: false,
imported: true,
// if style is imported then resource is the array of imported source files
resource: resources,
assetFile: outputFilename,
imports,
}
);

const resolveAssetFile = (match, file) =>
isAutoPublicPath ? Option.getAssetOutputFile(file, issuerFilename) : path.posix.join(publicPath, file);
if (!isCached) {
// 4. extracts CSS content from squashed sources
const issuerFilename = assetFile;

const cssContent = CssExtractModule.apply(sources, (content) => content.replace(urlRegex, resolveAssetFile));
const resolveAssetFile = (match, file) =>
isAutoPublicPath ? Option.getAssetOutputFile(file, issuerFilename) : path.posix.join(publicPath, file);

// 5. add extracted CSS file into compilation
const fileManifest = {
render: () => cssContent,
filename: assetFile,
identifier: `${pluginName}.${chunk.id}`,
hash,
};
const cssContent = CssExtractModule.apply(sources, (content) => content.replace(urlRegex, resolveAssetFile));

result.push(fileManifest);
// 5. add extracted CSS file into compilation
const fileManifest = {
render: () => cssContent,
filename: assetFile,
identifier: `${pluginName}.${chunk.id}`,
hash,
};

result.push(fileManifest);
}
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/Plugin/Collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ class Collection {
// note: in inlined style must be no LF character after the open tag, otherwise the mapping will not work
styleTags += `<style>` + source + `</style>${LF}`;
} else {
// note: void elements don't need the closing
// https://html.spec.whatwg.org/multipage/syntax.html#void-elements
linkTags += `<link href="${asset.assetFile}" rel="stylesheet">${LF}`;
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/Plugin/Option.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ class Option {
}

/**
* Whether the CSS resource should be inlined.
* Whether the CSS resource should be inlined, regard of the global css.inline option and the file query.
*
* @param {string} resource The resource file, including a query.
* @return {boolean}
Expand All @@ -432,8 +432,10 @@ class Option {
const [, query] = resource.split('?', 2);
const urlParams = new URLSearchParams(query);
const value = urlParams.get('inline');
const hasQueryInline = value != null;
const isInlinedByQuery = this.toBool(value, false, false);

return this.toBool(value, false, this.options.css.inline);
return (this.options.css.inline && (isInlinedByQuery || !hasQueryInline)) || isInlinedByQuery;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@
color: indianred;
}
</style>
<link href="main.dc4ea4af.css" rel="stylesheet">
<link href="main.5b01e96e.css" rel="stylesheet">
<style>.royal-blue {
border: 5px solid royalblue;
color: royalblue;
}
</style>
<script src="main.9da2f4dd.js" async defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<div class="red">Red</div>
<div class="lime-green">Green</div>
<div class="royal-blue">Blue</div>
<div class="hotpink">Pink</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.hotpink {
border: 5px solid hotpink;
color: hotpink;
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ <h1>Hello World!</h1>
<div class="red">Red</div>
<div class="lime-green">Green</div>
<div class="royal-blue">Blue</div>
<div class="hotpink">Pink</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// import as CSSStyleSheet object
import sheet from './style-c.css?sheet'; // `css-loader` option `exportType: 'css-style-sheet'`
import './style-d.css'; // `css-loader` option `exportType: 'array'`

// the extracted CSS will be injected into HTML
import './style-d.css?inline'; // `css-loader` option `exportType: 'array'`

// the extracted CSS will be saved into separate output file
import './style-e.css'; // `css-loader` option `exportType: 'array'`

console.log('sheet: ', sheet);

document.adoptedStyleSheets = [sheet];
//shadowRoot.adoptedStyleSheets = [sheet]; // error in browser: shadowRoot is not defined
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.hotpink {
border: 5px solid hotpink;
color: hotpink;
}
Loading

0 comments on commit 19eb206

Please sign in to comment.