Skip to content

Commit

Permalink
docs: simplify import map generation (#1308)
Browse files Browse the repository at this point in the history
Hopefully this is easier to understand and maintain

Depends on #1307
  • Loading branch information
bennypowers authored Nov 9, 2023
1 parent 9aebe54 commit 71535ca
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 99 deletions.
22 changes: 11 additions & 11 deletions docs/_includes/layout-base.njk
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@

{%- set extraPageClasses = bodyClasses or "" -%}
{%- if title -%}
{% set extraPageClasses = extraPageClasses + ' page-' + title | slug -%}
{%- endif -%}
<!doctype html>
<html class="no-js" lang="">

<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
Expand Down Expand Up @@ -35,7 +37,7 @@
</noscript>

<script type="importmap">{{ importMap | dump | safe }}</script>
<script async src="https://ga.jspm.io/npm:es-module-shims@1.7.1/dist/es-module-shims.js"></script>
<script async src="https://ga.jspm.io/npm:es-module-shims@1.8.0/dist/es-module-shims.js"></script>
{% for tag in importElements %}
<script type="module">
import '@rhds/elements/{{tag}}/{{tag}}.js';
Expand All @@ -52,17 +54,15 @@
document.documentElement.classList.add('js');
</script>
<script type="module" src="{{ '/assets/copy-permalink.js' | url }}"></script>
<script type="module">
import { PfIcon } from '@patternfly/elements/pf-icon/pf-icon.js';
PfIcon.getIconUrl = (set, icon) =>
new URL(`/assets/packages/@patternfly/elements/pf-icon/icons/${set}/${icon}.js`, location.origin);
</script>
</head>

{% set extraPageClasses = bodyClasses or "" %}
{% if title %}
{% set extraPageClasses = extraPageClasses + ' page--' + title | slug %}
{% endif %}
<body class="page {{ extraPageClasses }}" id="top" unresolved>
<body id="top" class="page {{ extraPageClasses }}" unresolved>
<div class="body-inner">

{{ content | safe }}

</div>
</body>
</html>
2 changes: 1 addition & 1 deletion docs/_includes/layout-demo.njk
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<link rel="stylesheet" href="{{ '/assets/base.css' | url }}">

<script type="importmap">{{ importMap | dump | safe }}</script>
<script async src="https://ga.jspm.io/npm:[email protected].0/dist/es-module-shims.js"></script>
<script async src="https://ga.jspm.io/npm:[email protected].1/dist/es-module-shims.js"></script>
<script type="module">
import 'element-internals-polyfill';
</script>
Expand Down
113 changes: 48 additions & 65 deletions docs/_plugins/importMap.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @ts-check
const { join } = require('node:path');
const { pathToFileURL } = require('node:url');
const { glob } = require('glob');
const { AssetCache } = require('@11ty/eleventy-fetch');

function logPerf() {
Expand All @@ -10,18 +9,15 @@ function logPerf() {
const chalk = require('chalk');
const TOTAL = performance.measure('importMap-total', 'importMap-start', 'importMap-end');
const RESOLVE = performance.measure('importMap-resolve', 'importMap-start', 'importMap-afterLocalPackages');
const TRACE = performance.measure('importMap-trace', 'importMap-afterLocalPackages', 'importMap-afterRHDSTraces');
if (TOTAL.duration > 2000) {
console.log(
`🦥 Import map generator done in ${chalk.red(TOTAL.duration)}ms\n`,
` Resolving local packages took ${chalk.red(RESOLVE.duration)}ms\n`,
` Tracing RHDS sources took ${chalk.red(TRACE.duration)}ms`,
);
} else if (TOTAL.duration > 1000) {
console.log(
`🐢 Import map generator done in ${chalk.yellow(TOTAL.duration)}ms\n`,
` Resolving local packages took ${chalk.yellow(RESOLVE.duration)}ms\n`,
` Tracing RHDS sources took ${chalk.yellow(TRACE.duration)}ms`,
);
} else {
console.log(
Expand All @@ -31,14 +27,27 @@ function logPerf() {
/* eslint-enable no-console */
}

/**
* @typedef {Object} Options
*
* @property {string} [defaultProvider]
* @property {import('@jspm/generator').Generator['importMap']} [inputMap]
* @property {import('@jspm/generator').Generator['importMap']} [manualImportMap]
* @property {string[]} [localPackages=[]]
* @property {string} nodemodulesPublicPath
* @property {string} cwd
* @property {AssetCache} assetCache
*/

/** @param {Options} opts */
async function getCachedImportMap({
defaultProvider,
inputMap,
specs,
localPackages,
elementsDir,
localPackages = [],
manualImportMap,
cwd,
assetCache,
nodemodulesPublicPath,
}) {
if (assetCache.isCacheValid('1d')) {
return assetCache.getCachedValue();
Expand All @@ -48,56 +57,35 @@ async function getCachedImportMap({

const { Generator } = await import('@jspm/generator');

const nothing = Symbol();
const providers = {
'@patternfly': 'nodemodules',
...Object.fromEntries(localPackages?.map(packageName =>
packageName.match(/@(rhds|patternfly)/) ? [nothing] : [packageName, 'nodemodules']) ?? []),
};

delete providers[nothing];

const generator = new Generator({
env: ['production', 'browser', 'module'],
defaultProvider,
inputMap,
providers: {
...Object.fromEntries(specs.map(x => [x.packageName, 'nodemodules'])),
},
providers,
});

await generator.install(localPackages);
performance.mark('importMap-afterLocalPackages');
await generator.install(localPackages.filter(x => !x.endsWith('/')));

// RHDS imports
// TODO: make rhds a 'package' like the other localPackages
const traces = [];
for (const x of await glob('./*/*.ts', { cwd: elementsDir, dotRelative: true, ignore: '**/*.d.ts' })) {
traces.push(
generator.link(x.replace('./', elementsDir).replace('.ts', '.js')),
generator.link(x.replace('./', '@rhds/elements/').replace('.ts', '.js')),
);
}
await Promise.all(traces);

generator.importMap.replace(pathToFileURL(elementsDir).href, '/assets/packages/@rhds/elements/elements/');
generator.importMap.replace(pathToFileURL(elementsDir).href.replace('elements', 'lib'), '/assets/packages/@rhds/elements/lib/');
performance.mark('importMap-afterRHDSTraces');

generator.importMap.set('@rhds/elements/lib/', '/assets/packages/@rhds/elements/lib/');
performance.mark('importMap-afterLocalPackages');

// Node modules
generator.importMap.replace(pathToFileURL(join(cwd, 'node_modules/')).href, '/assets/packages/');
generator.importMap.replace(pathToFileURL(join(cwd, 'node_modules/')).href, `${nodemodulesPublicPath}/`);

// for some reason, `@lrnwebcomponents/code-sample` shows up in the import map under cwd scope
generator.importMap.replace(`${pathToFileURL(cwd).href}/`, '/assets/packages/');
for (const [k, v] of Object.entries(manualImportMap?.imports ?? {})) {
generator.importMap.set(k, v);
}

const json = generator.importMap.flatten().combineSubpaths().toJSON();

// HACK: extract the scoped imports to the main map, since they're all local
// this might not be necessary if we flatten to a single lit version
Object.assign(json.imports ?? {}, Object.values(json.scopes ?? {}).find(x => 'lit-html' in x));
// ENDHACK

// TODO: automate this
Object.assign(json.imports ?? {}, {
'@rhds/tokens/': '/assets/packages/@rhds/tokens/js/',
'@rhds/elements/lib/': '/assets/packages/@rhds/elements/lib/',
'@rhds/elements/lib/context/': '/assets/packages/@rhds/elements/lib/context/',
'@rhds/elements/lib/context/color/': '/assets/packages/@rhds/elements/lib/context/color/',
});

performance.mark('importMap-end');

logPerf();
Expand All @@ -114,42 +102,37 @@ async function getCachedImportMap({
}
}

/**
* @param {import('@11ty/eleventy').UserConfig} eleventyConfig
* @param {Options} options
*/
module.exports = function(eleventyConfig, {
inputMap = undefined,
defaultProvider = undefined,
inputMap,
manualImportMap,
defaultProvider,
nodemodulesPublicPath,
localPackages = [],
} = {}) {
}) {
const cwd = process.cwd();
const elementsDir = join(cwd, 'elements/');

const specs = localPackages.map(spec => ({
spec,
packageName: spec.replace(/^@/, '$').replace(/@.*$/, '').replace(/^\$/, '@')
}));

// copy over local packages
for (const { packageName } of specs) {
eleventyConfig.addPassthroughCopy({ [`node_modules/${packageName}`]: `/assets/packages/${packageName}` });
}

// HACK: copy lit transitive deps
// this might not be necessary if we flatten to a single lit version
for (const packageName of ['lit-html', 'lit-element']) {
eleventyConfig.addPassthroughCopy({ [`node_modules/${packageName}`]: `/assets/packages/${packageName}` });
//
for (const spec of localPackages) {
const packageName = spec.replace(/^@/, '$').replace(/@.*$/, '').replace(/^\$/, '@');
eleventyConfig.addPassthroughCopy({ [`node_modules/${packageName}`]: `${nodemodulesPublicPath}/${packageName}` });
}
// ENDHACK

const assetCache = new AssetCache('rhds-ux-dot-import-map');

eleventyConfig.addGlobalData('importMap', async function cacheImportMap() {
return getCachedImportMap({
defaultProvider,
manualImportMap,
nodemodulesPublicPath,
inputMap,
specs,
localPackages,
elementsDir,
cwd,
assetCache,
assetCache
});
});

Expand Down
49 changes: 28 additions & 21 deletions eleventy.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ module.exports = function(eleventyConfig) {
eleventyConfig.addPassthroughCopy('docs/.nojekyll');
eleventyConfig.addPassthroughCopy('docs/robots.txt');
eleventyConfig.addPassthroughCopy('docs/assets/**/*');
eleventyConfig.addPassthroughCopy({ 'elements': 'assets/packages/@rhds/elements/elements/' });
eleventyConfig.addPassthroughCopy({ 'lib': 'assets/packages/@rhds/elements/lib/' });

eleventyConfig.on('eleventy.before', function({ runMode }) {
eleventyConfig.addGlobalData('runMode', runMode);
});

eleventyConfig.addPlugin(RHDSMarkdownItPlugin);

Expand All @@ -57,37 +59,42 @@ module.exports = function(eleventyConfig) {

/** Bespoke import map for ux-dot pages and demos */
eleventyConfig.addPassthroughCopy({ 'node_modules/@lit/reactive-element': '/assets/packages/@lit/reactive-element' });
eleventyConfig.addPassthroughCopy({ 'elements': 'assets/packages/@rhds/elements/elements/' });
eleventyConfig.addPassthroughCopy({ 'lib': 'assets/packages/@rhds/elements/lib/' });
eleventyConfig.addPlugin(ImportMapPlugin, {
defaultProvider: 'nodemodules',
nodemodulesPublicPath: '/assets/packages',
manualImportMap: {
imports: {
'@rhds/tokens/': '/assets/packages/@rhds/tokens/js/',
'@rhds/elements/': '/assets/packages/@rhds/elements/elements/',
'@rhds/elements/lib/': '/assets/packages/@rhds/elements/lib/',
'@patternfly/elements/': '/assets/packages/@patternfly/elements/',
'@patternfly/icons/': '/assets/packages/@patternfly/icons/',
'@patternfly/pfe-core/': '/assets/packages/@patternfly/pfe-core/',
}
},
localPackages: [
// ux-dot dependencies
'fuse.js',
'element-internals-polyfill',

// RHDS dependencies
// `manualImportMap` is not traced, so we need to manually specify these
//
// 1st party
'@rhds/tokens',
'@rhds/tokens/media.js',
'@rhds/tokens/meta.js',
'@patternfly/elements',
'@patternfly/pfe-core',
// Vendor
'lit',
'lit-html',
'lit-element',
'@lit/reactive-element',
'tslib',
'@floating-ui/dom',
'@floating-ui/core',

// RHDS modules
'@rhds/tokens',
'@rhds/tokens/media.js',
'@rhds/tokens/meta.js',
'@patternfly/pfe-core',
'@patternfly/elements',

// extra modules used in demo that didn't get picked up in the sources trace
// future solution could be to inject maps into each page in a transform
// but that could be prohibitively expensive if it has to call out to network for each page
// SEE: https://github.com/jspm/generator#generating-html
'@patternfly/elements/pf-panel/pf-panel.js',
'@patternfly/elements/pf-button/pf-button.js',
'@patternfly/elements/pf-card/pf-card.js',
'@patternfly/elements/pf-icon/pf-icon.js',
'@patternfly/elements/pf-spinner/pf-spinner.js',
'@patternfly/elements/pf-tabs/pf-tabs.js',
],
});

Expand Down
2 changes: 1 addition & 1 deletion spandx.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async function injectLocalSources(_req, res, next) {
}

chunk = chunk
.replace('</head>', `<script type="importmap">${importMapJson}</script><script async src="https://ga.jspm.io/npm:es-module-shims@1.5.1/dist/es-module-shims.js" crossorigin="anonymous"></script>\n</head>`)
.replace('</head>', `<script type="importmap">${importMapJson}</script><script async src="https://ga.jspm.io/npm:es-module-shims@1.8.0/dist/es-module-shims.js" crossorigin="anonymous"></script>\n</head>`)
.replace('</body>', `${proxyContents}\n\n</body>`);

// res.setHeader('Content-Length', chunk.length);
Expand Down

0 comments on commit 71535ca

Please sign in to comment.