Skip to content

Commit

Permalink
feature/issue 923 native import attributes for CSS and JSON (#1215)
Browse files Browse the repository at this point in the history
* intial draft of import attributes support for CSS and JSON

* all test cases passing

* need patch package

* wcc patches for import attributes and CSSStylesheet shim

* bump min NodeJS version for exp specs

* temp disable ESLint

* develop based import assertion specs

* serve based import attributes specs

* add preIntercept resource plugin lifecycle and refactor PostCSS to use it

* all test cases passing for import attributes support

* refactor built in CSS and JSON intercepting

* demo code

* raw plugin docs and package.json updates

* update latest documentation for custom loaders support in NodeJS

* update custom import docs

* upgrade wcc v0.13.0

* only need Node 18 for github actions

* css imports and raw plugin interop with test cases

* lit renderer import attribute test cases and documentation

* refactor matchers support for raw plugin instead of patching and add test cases

* disable describe.only

* update usage for custom resource plugins to showcase usage of import attributes

* document preIntercept lifecycle and convert Babel to use it

* restore ESLint

* enable debug logging for failing specs

* refactor theme pack specs

* fix linting

* remove CSS and JSON packages from being publishable

* clean up console logs and comments

* rename exp test cases to loadersnaming prefix

* fix command in github actions

* remove plugin-import-css callout from plugin-postcss README

* remove demo code from website

* refine PostCSS plugin intercepting
  • Loading branch information
thescientist13 authored Apr 29, 2024
1 parent d19a566 commit b420c5c
Show file tree
Hide file tree
Showing 147 changed files with 2,777 additions and 302 deletions.
23 changes: 18 additions & 5 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
// need this custom parser configuration until ESLint natively supports import attributes
// https://github.com/eslint/eslint/discussions/15305#discussioncomment-2508948
module.exports = {
parser: '@typescript-eslint/parser',
parser: '@babel/eslint-parser',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module'
ecmaVersion: 2022,
sourceType: 'module',
requireConfigFile: false,
ecmaFeatures: {
jsx: true
},
babelOptions: {
plugins: [
'@babel/plugin-syntax-import-assertions'
],
presets: ['@babel/preset-react']
}
},
plugins: [
'@typescript-eslint',
'no-only-tests'
],
extends: 'plugin:markdown/recommended',
// plugin does not seem to work well with custom parsers?
// https://github.com/eslint/eslint-plugin-markdown/discussions/221
// extends: 'plugin:markdown/recommended-legacy',
env: {
browser: true,
node: false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Continuous Integration (Experimental)
name: Continuous Integration (Loaders)

on: [pull_request]

Expand All @@ -25,4 +25,4 @@ jobs:
yarn install --frozen-lockfile && yarn lerna bootstrap
- name: Test
run: |
yarn test:exp
yarn test:loaders
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Continuous Integration Windows (Experimental)
name: Continuous Integration Windows (Loaders)

on: [pull_request]

Expand All @@ -22,4 +22,4 @@ jobs:
yarn install --frozen-lockfile --network-timeout 1000000 && yarn lerna bootstrap
- name: Test
run: |
yarn test:exp:win
yarn test:loaders:win
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
.vscode/
coverage/
node_modules/
packages/init/test/**/my-app
packages/**/test/**/yarn.lock
packages/**/test/**/package-lock.json
packages/**/test/**/netlify
Expand Down
1 change: 1 addition & 0 deletions .ls-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ ls:
ignore:
- .git
- node_modules
- packages/plugin-babel/node_modules
- packages/init/node_modules
- packages/plugin-typescript/node_modules
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.12.1
18.20.0
11 changes: 7 additions & 4 deletions greenwood.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { greenwoodPluginGraphQL } from '@greenwood/plugin-graphql';
import { greenwoodPluginIncludeHTML } from '@greenwood/plugin-include-html';
import { greenwoodPluginImportCss } from '@greenwood/plugin-import-css';
import { greenwoodPluginImportJson } from '@greenwood/plugin-import-json';
import { greenwoodPluginPolyfills } from '@greenwood/plugin-polyfills';
import { greenwoodPluginPostCss } from '@greenwood/plugin-postcss';
import { greenwoodPluginImportRaw } from '@greenwood/plugin-import-raw';
import { greenwoodPluginRendererPuppeteer } from '@greenwood/plugin-renderer-puppeteer';
import rollupPluginAnalyzer from 'rollup-plugin-analyzer';

Expand All @@ -18,8 +17,12 @@ export default {
lit: true
}),
greenwoodPluginPostCss(),
greenwoodPluginImportJson(),
greenwoodPluginImportCss(),
greenwoodPluginImportRaw({
matches: [
'eve-button.css',
'eve-container.css'
]
}),
greenwoodPluginIncludeHTML(),
greenwoodPluginRendererPuppeteer(),
{
Expand Down
16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,24 @@
"build": "cross-env __GWD_ROLLUP_MODE__=strict node . build",
"serve": "node . serve",
"develop": "node . develop",
"test": "cross-env BROWSERSLIST_IGNORE_OLD_DATA=true __GWD_ROLLUP_MODE__=strict NODE_NO_WARNINGS=1 c8 mocha --exclude \"./packages/**/test/cases/exp-*/**\" \"./packages/**/**/*.spec.js\"",
"test:exp": "cross-env BROWSERSLIST_IGNORE_OLD_DATA=true __GWD_ROLLUP_MODE__=strict NODE_NO_WARNINGS=1 node --experimental-loader $(pwd)/test/test-loader.js ./node_modules/mocha/bin/mocha \"./packages/**/**/*.spec.js\"",
"test:exp:win": "cross-env BROWSERSLIST_IGNORE_OLD_DATA=true __GWD_ROLLUP_MODE__=strict NODE_NO_WARNINGS=1 node --experimental-loader file:\\\\%cd%\\test\\test-loader.js ./node_modules/mocha/bin/mocha --exclude \"./packages/init/test/cases/**\" \"./packages/**/**/*.spec.js\"",
"test": "cross-env BROWSERSLIST_IGNORE_OLD_DATA=true __GWD_ROLLUP_MODE__=strict NODE_NO_WARNINGS=1 c8 mocha --exclude \"./packages/**/test/cases/loaders-*/**\" \"./packages/**/**/*.spec.js\"",
"test:loaders": "cross-env BROWSERSLIST_IGNORE_OLD_DATA=true __GWD_ROLLUP_MODE__=strict NODE_NO_WARNINGS=1 node --loader $(pwd)/test/test-loader.js ./node_modules/mocha/bin/mocha \"./packages/**/**/*.spec.js\"",
"test:loaders:win": "cross-env BROWSERSLIST_IGNORE_OLD_DATA=true __GWD_ROLLUP_MODE__=strict NODE_NO_WARNINGS=1 node --loader file:\\\\%cd%\\test\\test-loader.js ./node_modules/mocha/bin/mocha --exclude \"./packages/init/test/cases/**\" \"./packages/**/**/*.spec.js\"",
"test:tdd": "yarn test --watch",
"lint:js": "eslint \"*.{js,md}\" \"./packages/**/**/*.{js,md}\" \"./test/*.js\" \"./www/**/**/*.{js,md}\"",
"lint:js": "eslint \"*.js\" \"./packages/**/**/*.js\" \"./test/*.js\" \"./www/**/**/*.js\"",
"lint:ts": "eslint \"./packages/**/**/*.ts\"",
"lint:css": "stylelint \"./www/**/*.js\", \"./www/**/*.css\"",
"lint": "ls-lint && yarn lint:js && yarn lint:ts && yarn lint:css"
"lint": "ls-lint && yarn lint:js && yarn lint:css"
},
"resolutions": {
"lit": "^3.1.0"
},
"devDependencies": {
"@babel/core": "^7.24.4",
"@babel/eslint-parser": "^7.24.1",
"@babel/plugin-syntax-import-assertions": "^7.24.1",
"@babel/preset-react": "^7.24.1",
"@ls-lint/ls-lint": "^1.10.0",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"babel-eslint": "^10.1.0",
"c8": "^7.10.0",
"chai": "^4.2.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"NodeJS"
],
"engines": {
"node": ">=18.12.1"
"node": ">=18.20.0"
},
"bin": {
"greenwood": "./src/index.js"
Expand All @@ -38,7 +38,7 @@
"acorn-walk": "^8.0.0",
"commander": "^2.20.0",
"css-tree": "^2.2.1",
"es-module-shims": "^1.2.0",
"es-module-shims": "^1.8.3",
"front-matter": "^4.0.2",
"koa": "^2.13.0",
"koa-body": "^6.0.1",
Expand All @@ -52,7 +52,7 @@
"remark-rehype": "^7.0.0",
"rollup": "^3.29.4",
"unified": "^9.2.0",
"wc-compiler": "~0.12.1"
"wc-compiler": "~0.13.0"
},
"devDependencies": {
"@babel/runtime": "^7.10.4",
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ async function interceptPage(url, request, plugins, body) {
});

for (const plugin of plugins) {
if (plugin.shouldPreIntercept && await plugin.shouldPreIntercept(url, request, response)) {
response = await plugin.preIntercept(url, request, response);
}

if (plugin.shouldIntercept && await plugin.shouldIntercept(url, request, response)) {
response = await plugin.intercept(url, request, response);
}
Expand Down
75 changes: 53 additions & 22 deletions packages/cli/src/config/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as walk from 'acorn-walk';
// https://github.com/rollup/rollup/issues/2121
// would be nice to get rid of this
function cleanRollupId(id) {
return id.replace('\x00', '');
return id.replace('\x00', '').replace('?commonjs-proxy', '');
}

function greenwoodResourceLoader (compilation) {
Expand All @@ -35,25 +35,42 @@ function greenwoodResourceLoader (compilation) {
}
},
async load(id) {
const idUrl = new URL(`file://${cleanRollupId(id)}`);
let idUrl = new URL(`file://${cleanRollupId(id)}`);
const { pathname } = idUrl;
const extension = pathname.split('.').pop();
const headers = {
'Accept': 'text/javascript',
'Sec-Fetch-Dest': 'empty'
};

// filter first for any bare specifiers
if (await checkResourceExists(idUrl) && extension !== '' && extension !== 'js') {
const url = new URL(`${idUrl.href}?type=${extension}`);
const request = new Request(url.href);
if (await checkResourceExists(idUrl) && extension !== 'js') {
for (const plugin of resourcePlugins) {
if (plugin.shouldResolve && await plugin.shouldResolve(idUrl)) {
idUrl = new URL((await plugin.resolve(idUrl)).url);
}
}

const request = new Request(idUrl, {
headers
});
let response = new Response('');

for (const plugin of resourcePlugins) {
if (plugin.shouldServe && await plugin.shouldServe(url, request)) {
response = await plugin.serve(url, request);
if (plugin.shouldServe && await plugin.shouldServe(idUrl, request)) {
response = await plugin.serve(idUrl, request);
}
}

for (const plugin of resourcePlugins) {
if (plugin.shouldIntercept && await plugin.shouldIntercept(url, request, response.clone())) {
response = await plugin.intercept(url, request, response.clone());
if (plugin.shouldPreIntercept && await plugin.shouldPreIntercept(idUrl, request, response.clone())) {
response = await plugin.preIntercept(idUrl, request, response.clone());
}
}

for (const plugin of resourcePlugins) {
if (plugin.shouldIntercept && await plugin.shouldIntercept(idUrl, request, response.clone())) {
response = await plugin.intercept(idUrl, request, response.clone());
}
}

Expand Down Expand Up @@ -161,26 +178,42 @@ function greenwoodImportMetaUrl(compilation) {
});
const idAssetName = path.basename(id);
const normalizedId = id.replace(/\\\\/g, '/').replace(/\\/g, '/'); // windows shenanigans...
const idUrl = new URL(`file://${cleanRollupId(id)}`);
const { pathname } = idUrl;
const extension = pathname.split('.').pop();
const urlWithType = new URL(`${idUrl.href}?type=${extension}`);
const request = new Request(urlWithType.href);
let idUrl = new URL(`file://${cleanRollupId(id)}`);
const headers = {
'Accept': 'text/javascript',
'Sec-Fetch-Dest': 'empty'
};
const request = new Request(idUrl, {
headers
});
let canTransform = false;
let response = new Response(code);

// handle any custom imports or pre-processing needed before passing to Rollup this.parse
if (await checkResourceExists(idUrl) && extension !== '' && extension !== 'json') {
if (await checkResourceExists(idUrl)) {
for (const plugin of resourcePlugins) {
if (plugin.shouldResolve && await plugin.shouldResolve(idUrl)) {
idUrl = new URL((await plugin.resolve(idUrl)).url);
}
}

for (const plugin of resourcePlugins) {
if (plugin.shouldServe && await plugin.shouldServe(idUrl, request)) {
response = await plugin.serve(idUrl, request);
canTransform = true;
}
}

for (const plugin of resourcePlugins) {
if (plugin.shouldServe && await plugin.shouldServe(urlWithType, request)) {
response = await plugin.serve(urlWithType, request);
if (plugin.shouldPreIntercept && await plugin.shouldPreIntercept(idUrl, request, response)) {
response = await plugin.preIntercept(idUrl, request, response);
canTransform = true;
}
}

for (const plugin of resourcePlugins) {
if (plugin.shouldIntercept && await plugin.shouldIntercept(urlWithType, request, response.clone())) {
response = await plugin.intercept(urlWithType, request, response.clone());
if (plugin.shouldIntercept && await plugin.shouldIntercept(idUrl, request, response.clone())) {
response = await plugin.intercept(idUrl, request, response.clone());
canTransform = true;
}
}
Expand All @@ -201,11 +234,9 @@ function greenwoodImportMetaUrl(compilation) {
const absoluteScriptDir = path.dirname(id);
const relativeAssetPath = getMetaImportPath(node);
const absoluteAssetPath = path.resolve(absoluteScriptDir, relativeAssetPath);
const assetName = path.basename(absoluteAssetPath);
const assetExtension = assetName.split('.').pop();

assetUrls.push({
url: new URL(`file://${absoluteAssetPath}?type=${assetExtension}`),
url: new URL(`file://${absoluteAssetPath}`),
relativeAssetPath
});
}
Expand Down
11 changes: 7 additions & 4 deletions packages/cli/src/lib/resource-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,18 @@ function mergeResponse(destination, source) {
// https://github.com/rollup/rollup/issues/3779
function normalizePathnameForWindows(url) {
const windowsDriveRegex = /\/[a-zA-Z]{1}:\//;
const { pathname = '' } = url;
const { pathname = '', searchParams } = url;
const params = searchParams.size > 0
? `?${searchParams.toString()}`
: '';

if (windowsDriveRegex.test(pathname)) {
const driveMatch = pathname.match(windowsDriveRegex)[0];

return pathname.replace(driveMatch, driveMatch.replace('/', ''));
return `${pathname.replace(driveMatch, driveMatch.replace('/', ''))}${params}`;
}

return pathname;
return `${pathname}${params}`;
}

async function checkResourceExists(url) {
Expand Down Expand Up @@ -108,7 +111,7 @@ async function resolveForRelativeUrl(url, rootUrl) {
return reducedUrl;
}

// TODO does this make more sense in bundle lifecycle?
// does this make more sense in bundle lifecycle?
// https://github.com/ProjectEvergreen/greenwood/issues/970
// or could this be done sooner (like in appTemplate building in html resource plugin)?
// Or do we need to ensure userland code / plugins have gone first
Expand Down
25 changes: 23 additions & 2 deletions packages/cli/src/lifecycles/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ async function interceptPage(url, request, plugins, body) {
});

for (const plugin of plugins) {
if (plugin.shouldPreIntercept && await plugin.shouldPreIntercept(url, request, response)) {
response = await plugin.preIntercept(url, request, response);
}

if (plugin.shouldIntercept && await plugin.shouldIntercept(url, request, response)) {
response = await plugin.intercept(url, request, response);
}
Expand Down Expand Up @@ -140,11 +144,27 @@ async function bundleStyleResources(compilation, resourcePlugins) {
} else {
const url = resource.sourcePathURL;
const contentType = 'text/css';
const headers = new Headers({ 'Content-Type': contentType });
const headers = new Headers({ 'Content-Type': contentType, 'Accept': contentType });
const request = new Request(url, { headers });
const initResponse = new Response(contents, { headers });

let response = await resourcePlugins.reduce(async (responsePromise, plugin) => {
const intermediateResponse = await responsePromise;
const shouldPreIntercept = plugin.shouldPreIntercept && await plugin.shouldPreIntercept(url, request, intermediateResponse.clone());

if (shouldPreIntercept) {
const currentResponse = await plugin.preIntercept(url, request, intermediateResponse.clone());
const mergedResponse = mergeResponse(intermediateResponse.clone(), currentResponse.clone());

if (mergedResponse.headers.get('Content-Type').indexOf(contentType) >= 0) {
return Promise.resolve(mergedResponse.clone());
}
}

return Promise.resolve(responsePromise);
}, Promise.resolve(initResponse));

response = await resourcePlugins.reduce(async (responsePromise, plugin) => {
const intermediateResponse = await responsePromise;
const shouldIntercept = plugin.shouldIntercept && await plugin.shouldIntercept(url, request, intermediateResponse.clone());

Expand All @@ -158,7 +178,7 @@ async function bundleStyleResources(compilation, resourcePlugins) {
}

return Promise.resolve(responsePromise);
}, Promise.resolve(initResponse));
}, Promise.resolve(response.clone()));

response = await resourcePlugins.reduce(async (responsePromise, plugin) => {
const intermediateResponse = await responsePromise;
Expand Down Expand Up @@ -295,6 +315,7 @@ const bundleCompilation = async (compilation) => {
return plugin.provider(compilation);
}).filter((provider) => {
return provider.shouldIntercept && provider.intercept
|| provider.shouldPreIntercept && provider.preIntercept
|| provider.shouldOptimize && provider.optimize;
});

Expand Down
Loading

0 comments on commit b420c5c

Please sign in to comment.