-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(*): initial implementation based on https://github.com/bazelbuil…
- Loading branch information
Showing
9 changed files
with
307 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
# html-insert-assets | ||
Insert assets such as .js, .css into an HTML file. | ||
|
||
Originally forked from https://github.com/bazelbuild/rules_nodejs/tree/0.41.0/packages/inject-html. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Originally forked from https://github.com/bazelbuild/rules_nodejs/tree/0.41.0/packages/inject-html | ||
|
||
const parse5 = require('parse5'); | ||
const treeAdapter = require('parse5/lib/tree-adapters/default'); | ||
const fs = require('fs'); | ||
|
||
function findElementByName(d, name) { | ||
if (treeAdapter.isTextNode(d)) return undefined; | ||
if (d.tagName && d.tagName.toLowerCase() === name) { | ||
return d; | ||
} | ||
if (!treeAdapter.getChildNodes(d)) { | ||
return undefined; | ||
} | ||
for (let i = 0; i < treeAdapter.getChildNodes(d).length; i++) { | ||
const f = treeAdapter.getChildNodes(d)[i]; | ||
const result = findElementByName(f, name); | ||
if (result) return result; | ||
} | ||
return undefined; | ||
} | ||
|
||
function main(params, read = fs.readFileSync, write = fs.writeFileSync, timestamp = Date.now) { | ||
const outputFile = params.shift(); | ||
const inputFile = params.shift(); | ||
const rootDirs = []; | ||
while (params.length && params[0] !== '--assets') { | ||
let r = params.shift(); | ||
if (!r.endsWith('/')) { | ||
r += '/'; | ||
} | ||
rootDirs.push(r); | ||
} | ||
// Always trim the longest prefix | ||
rootDirs.sort((a, b) => b.length - a.length); | ||
params.shift(); // --assets | ||
|
||
const document = parse5.parse(read(inputFile, {encoding: 'utf-8'}), {treeAdapter}); | ||
|
||
const body = findElementByName(document, 'body'); | ||
if (!body) { | ||
throw ('No <body> tag found in HTML document'); | ||
} | ||
|
||
const head = findElementByName(document, 'head'); | ||
if (!head) { | ||
throw ('No <head> tag found in HTML document'); | ||
} | ||
|
||
/** | ||
* Trims the longest prefix from the path | ||
*/ | ||
function relative(execPath) { | ||
if (execPath.startsWith('external/')) { | ||
execPath = execPath.substring('external/'.length); | ||
} | ||
for (const r of rootDirs) { | ||
if (execPath.startsWith(r)) { | ||
return execPath.substring(r.length); | ||
} | ||
} | ||
return execPath; | ||
} | ||
|
||
const jsFiles = params.filter(s => /\.m?js$/i.test(s)); | ||
for (const s of jsFiles) { | ||
// Differential loading: for filenames like | ||
// foo.mjs | ||
// bar.es2015.js | ||
// we use a <script type="module" tag so these are only run in browsers that have ES2015 module | ||
// loading | ||
if (/\.(es2015\.|m)js$/i.test(s)) { | ||
const moduleScript = treeAdapter.createElement('script', undefined, [ | ||
{name: 'type', value: 'module'}, | ||
{name: 'src', value: `/${relative(s)}?v=${timestamp()}`}, | ||
]); | ||
treeAdapter.appendChild(body, moduleScript); | ||
} else { | ||
// Other filenames we assume are for non-ESModule browsers, so if the file has a matching | ||
// ESModule script we add a 'nomodule' attribute | ||
function hasMatchingModule(file, files) { | ||
const noExt = file.substring(0, file.length - 3); | ||
const testMjs = (noExt + '.mjs').toLowerCase(); | ||
const testEs2015 = (noExt + '.es2015.js').toLowerCase(); | ||
const matches = files.filter(t => { | ||
const lc = t.toLowerCase(); | ||
return lc === testMjs || lc === testEs2015; | ||
}); | ||
return matches.length > 0; | ||
} | ||
|
||
// Note: empty string value is equivalent to a bare attribute, according to | ||
// https://github.com/inikulin/parse5/issues/1 | ||
const nomodule = hasMatchingModule(s, jsFiles) ? [{name: 'nomodule', value: ''}] : []; | ||
|
||
const noModuleScript = treeAdapter.createElement('script', undefined, nomodule.concat([ | ||
{name: 'src', value: `/${relative(s)}?v=${timestamp()}`}, | ||
])); | ||
treeAdapter.appendChild(body, noModuleScript); | ||
} | ||
} | ||
|
||
for (const s of params.filter(s => /\.css$/.test(s))) { | ||
const stylesheet = treeAdapter.createElement('link', undefined, [ | ||
{name: 'rel', value: 'stylesheet'}, | ||
{name: 'href', value: `/${relative(s)}?v=${timestamp()}`}, | ||
]); | ||
treeAdapter.appendChild(head, stylesheet); | ||
} | ||
|
||
const content = parse5.serialize(document, {treeAdapter}); | ||
write(outputFile, content, {encoding: 'utf-8'}); | ||
return 0; | ||
} | ||
|
||
module.exports = {main}; | ||
|
||
if (require.main === module) { | ||
// We always require the arguments are encoded into a flagfile | ||
// so that we don't exhaust the command-line limit. | ||
const params = fs.readFileSync(process.argv[2], {encoding: 'utf-8'}).split('\n').filter(l => !!l); | ||
process.exitCode = main(params); | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<html></html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<html><head><link rel="stylesheet" href="/file.css?v=123"></head><body><script src="/script.js?v=123"></script></body></html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
const el = document.createElement('div'); | ||
const text = 'Hello, World'; | ||
el.innerText = text; | ||
document.body.appendChild(el); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
const inserter = require('../src/main'); | ||
|
||
describe('HTML inserter', () => { | ||
const inFile = 'data/some/index.html'; | ||
const outFile = 'out/some/index.html'; | ||
|
||
let output; | ||
function read(file) { | ||
if (file === inFile) return `<html><head></head><body></body></html>`; | ||
throw new Error(`no content for ${file}`); | ||
} | ||
function write(_, content) { | ||
output = content; | ||
} | ||
|
||
it('should do be a no-op', () => { | ||
expect(inserter.main([outFile, inFile], read, write)).toBe(0); | ||
expect(output).toBe('<html><head></head><body></body></html>'); | ||
}); | ||
|
||
it('should inject script tag', () => { | ||
expect(inserter.main([outFile, inFile, '--assets', 'path/to/my.js'], read, write, () => 123)).toBe(0); | ||
expect(output).toBe( | ||
'<html><head></head><body><script src="/path/to/my.js?v=123"></script></body></html>'); | ||
}); | ||
|
||
it('should allow the "module js" extension', () => { | ||
expect(inserter.main([outFile, inFile, '--assets', 'path/to/my.mjs'], read, write, () => 123)) | ||
.toBe(0); | ||
expect(output).toBe( | ||
'<html><head></head><body><script type="module" src="/path/to/my.mjs?v=123"></script></body></html>'); | ||
}); | ||
|
||
it('should allow the ".es2015.js" extension', () => { | ||
expect(inserter.main( | ||
[outFile, inFile, '--assets', 'path/to/my.es2015.js'], read, write, () => 123)) | ||
.toBe(0); | ||
expect(output).toBe( | ||
'<html><head></head><body><script type="module" src="/path/to/my.es2015.js?v=123"></script></body></html>'); | ||
}); | ||
|
||
it('should strip longest prefix', () => { | ||
expect(inserter.main([outFile, inFile, | ||
'path', 'path/to', | ||
'--assets', 'path/to/my.js'], read, write, () => 123)).toBe(0); | ||
expect(output).toBe( | ||
'<html><head></head><body><script src="/my.js?v=123"></script></body></html>'); | ||
}); | ||
|
||
it('should strip external workspaces', () => { | ||
expect(inserter.main([outFile, inFile, | ||
'npm/node_modules/zone.js/dist', | ||
'--assets', 'external/npm/node_modules/zone.js/dist/zone.min.js'], read, write, () => 123)).toBe(0); | ||
expect(output).toBe( | ||
'<html><head></head><body><script src="/zone.min.js?v=123"></script></body></html>'); | ||
|
||
}); | ||
|
||
it('should inject link tag', () => { | ||
expect(inserter.main([outFile, inFile, '--assets', 'path/to/my.css'], read, write, () => 123)).toBe(0); | ||
expect(output).toBe( | ||
'<html><head><link rel="stylesheet" href="/path/to/my.css?v=123"></head><body></body></html>'); | ||
}); | ||
|
||
it('should create a pair of script tags for differential loading', () => { | ||
expect(inserter.main( | ||
[outFile, inFile, '--assets', 'path/to/my.js', 'path/to/my.es2015.js'], read, write, | ||
() => 123)) | ||
.toBe(0); | ||
expect(output).toBe( | ||
'<html><head></head><body><script nomodule="" src="/path/to/my.js?v=123"></script><script type="module" src="/path/to/my.es2015.js?v=123"></script></body></html>'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,92 @@ | |
# yarn lockfile v1 | ||
|
||
|
||
balanced-match@^1.0.0: | ||
version "1.0.0" | ||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" | ||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= | ||
|
||
brace-expansion@^1.1.7: | ||
version "1.1.11" | ||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" | ||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== | ||
dependencies: | ||
balanced-match "^1.0.0" | ||
concat-map "0.0.1" | ||
|
||
[email protected]: | ||
version "0.0.1" | ||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" | ||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= | ||
|
||
fs.realpath@^1.0.0: | ||
version "1.0.0" | ||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" | ||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= | ||
|
||
glob@^7.1.4: | ||
version "7.1.6" | ||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" | ||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== | ||
dependencies: | ||
fs.realpath "^1.0.0" | ||
inflight "^1.0.4" | ||
inherits "2" | ||
minimatch "^3.0.4" | ||
once "^1.3.0" | ||
path-is-absolute "^1.0.0" | ||
|
||
inflight@^1.0.4: | ||
version "1.0.6" | ||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" | ||
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= | ||
dependencies: | ||
once "^1.3.0" | ||
wrappy "1" | ||
|
||
inherits@2: | ||
version "2.0.4" | ||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" | ||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== | ||
|
||
jasmine-core@~3.5.0: | ||
version "3.5.0" | ||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4" | ||
integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA== | ||
|
||
jasmine@^3.5.0: | ||
version "3.5.0" | ||
resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.5.0.tgz#7101eabfd043a1fc82ac24e0ab6ec56081357f9e" | ||
integrity sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ== | ||
dependencies: | ||
glob "^7.1.4" | ||
jasmine-core "~3.5.0" | ||
|
||
minimatch@^3.0.4: | ||
version "3.0.4" | ||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" | ||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== | ||
dependencies: | ||
brace-expansion "^1.1.7" | ||
|
||
once@^1.3.0: | ||
version "1.4.0" | ||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" | ||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= | ||
dependencies: | ||
wrappy "1" | ||
|
||
parse5@^5.1.1: | ||
version "5.1.1" | ||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" | ||
integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== | ||
|
||
path-is-absolute@^1.0.0: | ||
version "1.0.1" | ||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" | ||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= | ||
|
||
wrappy@1: | ||
version "1.0.2" | ||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" | ||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= |