From c04eb904c5d117154d4ff09f5e20aedd778d2e78 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 9 Mar 2024 18:17:31 -0500 Subject: [PATCH 01/47] upgrade wcc v0.11.0 (#1200) --- packages/cli/package.json | 2 +- packages/plugin-import-jsx/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 605c481e3..fdfc3cd83 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -52,7 +52,7 @@ "remark-rehype": "^7.0.0", "rollup": "^2.58.0", "unified": "^9.2.0", - "wc-compiler": "~0.10.0" + "wc-compiler": "~0.11.0" }, "devDependencies": { "@babel/runtime": "^7.10.4", diff --git a/packages/plugin-import-jsx/package.json b/packages/plugin-import-jsx/package.json index cbbb223d6..78f751188 100644 --- a/packages/plugin-import-jsx/package.json +++ b/packages/plugin-import-jsx/package.json @@ -27,7 +27,7 @@ "@greenwood/cli": "^0.28.0-alpha.4" }, "dependencies": { - "wc-compiler": "~0.10.0" + "wc-compiler": "~0.11.0" }, "devDependencies": { "@greenwood/cli": "^0.29.4" diff --git a/yarn.lock b/yarn.lock index 88680a639..30ff4b322 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16787,10 +16787,10 @@ wait-port@1.0.4: commander "^9.3.0" debug "^4.3.4" -wc-compiler@~0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.10.0.tgz#3a0c32c500425c4493def7c47ea8df230d7cd0b8" - integrity sha512-GwtQw/cgbaCedoeguMc5YlRvHujDRTfpJxgglPAVT3LuJCRGXqG3nyckuV4mvblOBAPwePuP9T5nOTxbW/ZIRA== +wc-compiler@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.11.0.tgz#fc42d81f23eee0cc98d497aec574cf49c553ae03" + integrity sha512-2reo21bD1jbjqDKbN4rBZ07Om4riP58Llb8Wtc7H7s704Jm8t8yFMKVpNZh3S9/3GpkDZbwp6XKI2MmkYugafQ== dependencies: acorn "^8.7.0" acorn-jsx "^5.3.2" From 4bf08a262287fdc6089916a96ea58ddffd7fe835 Mon Sep 17 00:00:00 2001 From: Paul Barry Date: Sat, 9 Mar 2024 18:42:52 -0500 Subject: [PATCH 02/47] enhancement/Issue-1087: Upgrade Rollup to latest 3.x.x (#1187) * Issue-1087: Initial upgrade of Rollup and @rollup plugins * Issue-1087: Upgrade plugin dependencies * upgrade all test cases with new hash matchers * update all plugin rollup deps and breaking change updates * fix duplicate imports of eve-container custom element definition --------- Co-authored-by: Owen Buckley --- packages/cli/package.json | 10 +- .../build.default.import-node-modules.spec.js | 4 +- .../serve.config.base-path.spec.js | 2 +- .../serve.default.ssr.spec.js | 2 +- packages/plugin-babel/package.json | 2 +- packages/plugin-babel/src/index.js | 2 +- packages/plugin-import-commonjs/package.json | 4 +- .../test/cases/default/default.spec.js | 4 +- .../cases/build.default/build.default.spec.js | 2 +- www/components/banner/banner.js | 1 - www/components/header/header.js | 1 - yarn.lock | 218 +++++++++--------- 12 files changed, 129 insertions(+), 123 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index fdfc3cd83..95c5c400b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -30,10 +30,10 @@ "access": "public" }, "dependencies": { - "@rollup/plugin-commonjs": "^21.0.0", - "@rollup/plugin-node-resolve": "^13.0.0", - "@rollup/plugin-replace": "^2.3.4", - "@rollup/plugin-terser": "^0.1.0", + "@rollup/plugin-commonjs": "^25.0.0", + "@rollup/plugin-node-resolve": "^15.0.0", + "@rollup/plugin-replace": "^5.0.5", + "@rollup/plugin-terser": "^0.4.4", "acorn": "^8.0.1", "acorn-walk": "^8.0.0", "commander": "^2.20.0", @@ -50,7 +50,7 @@ "remark-frontmatter": "^2.0.0", "remark-parse": "^8.0.3", "remark-rehype": "^7.0.0", - "rollup": "^2.58.0", + "rollup": "^3.29.4", "unified": "^9.2.0", "wc-compiler": "~0.11.0" }, diff --git a/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js b/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js index ddb147b5d..736d93df5 100644 --- a/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js +++ b/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js @@ -235,14 +235,14 @@ describe('Build Greenwood With: ', function() { }); it('should have the expected lit related files in the output directory', async function() { - expect(await glob.promise(path.join(this.context.publicDir, 'lit-element.*.js'))).to.have.lengthOf(1); + expect(await glob.promise(path.join(this.context.publicDir, 'lit*.js'))).to.have.lengthOf(1); }); it('should have the expected inline node_modules content in the first inline script', async function() { const inlineScriptTag = Array.from(dom.window.document.querySelectorAll('head > script:not([src])')).filter(tag => !tag.getAttribute('data-gwd'))[0]; expect(inlineScriptTag.textContent.replace(/\n/g, '')).to - .equal('import"/lit-element.ae169679.js";import"/lit-html.7f7a9139.js";//# sourceMappingURL=116321042.6c5eb91c.js.map'); + .equal('import"/116321042.262925e6.js";import"/lit-html.71ac31d8.js";//# sourceMappingURL=116321042.f667a8c7.js.map'); }); }); diff --git a/packages/cli/test/cases/serve.config.base-path/serve.config.base-path.spec.js b/packages/cli/test/cases/serve.config.base-path/serve.config.base-path.spec.js index 99084c714..bad6e1dd5 100644 --- a/packages/cli/test/cases/serve.config.base-path/serve.config.base-path.spec.js +++ b/packages/cli/test/cases/serve.config.base-path/serve.config.base-path.spec.js @@ -48,7 +48,7 @@ describe('Serve Greenwood With: ', function() { const publicPath = path.join(outputPath, 'public/'); const hostname = 'http://127.0.0.1:8080'; const basePath = '/my-path'; - const jsHash = '4bcc801e'; + const jsHash = '2ce3f02d'; const cssHash = '1454013616'; let runner; diff --git a/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js b/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js index 393ca909a..424896114 100644 --- a/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js +++ b/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js @@ -364,7 +364,7 @@ describe('Serve Greenwood With: ', function() { }); describe('Bundled image using new URL and import.meta.url', function() { - const bundledName = 'assets/logo-abb2e884.svg'; + const bundledName = 'assets/logo-619de195.svg'; let response = {}; let body; let usersResponse = {}; diff --git a/packages/plugin-babel/package.json b/packages/plugin-babel/package.json index bed8fa82e..a6304848c 100644 --- a/packages/plugin-babel/package.json +++ b/packages/plugin-babel/package.json @@ -29,7 +29,7 @@ "@babel/core": "^7.10.4", "@babel/plugin-transform-runtime": "^7.10.4", "@babel/preset-env": "^7.10.4", - "@rollup/plugin-babel": "^5.3.0", + "@rollup/plugin-babel": "^6.0.4", "core-js": "^3.4.1" }, "devDependencies": { diff --git a/packages/plugin-babel/src/index.js b/packages/plugin-babel/src/index.js index 4afd739ae..d4881e915 100644 --- a/packages/plugin-babel/src/index.js +++ b/packages/plugin-babel/src/index.js @@ -61,7 +61,7 @@ const greenwoodPluginBabel = (options = {}) => { type: 'rollup', name: 'plugin-babel:rollup', provider: (compilation) => [ - rollupBabelPlugin.default({ + rollupBabelPlugin({ // https://github.com/rollup/plugins/tree/master/packages/babel#babelhelpers babelHelpers: 'bundled', diff --git a/packages/plugin-import-commonjs/package.json b/packages/plugin-import-commonjs/package.json index 99b1c18c8..ba86d7268 100644 --- a/packages/plugin-import-commonjs/package.json +++ b/packages/plugin-import-commonjs/package.json @@ -26,8 +26,8 @@ "@greenwood/cli": "^0.4.0" }, "dependencies": { - "@rollup/plugin-commonjs": "^21.0.0", - "@rollup/stream": "^2.0.0", + "@rollup/plugin-commonjs": "^25.0.0", + "@rollup/stream": "^3.0.1", "cjs-module-lexer": "^1.0.0" }, "devDependencies": { diff --git a/packages/plugin-import-commonjs/test/cases/default/default.spec.js b/packages/plugin-import-commonjs/test/cases/default/default.spec.js index 05b49328a..ec2a698e2 100644 --- a/packages/plugin-import-commonjs/test/cases/default/default.spec.js +++ b/packages/plugin-import-commonjs/test/cases/default/default.spec.js @@ -96,8 +96,8 @@ describe('Build Greenwood With: ', function() { it('should have the expected CommonJS contents from main.js (lodash) in the output', async function() { const contents = fs.readFileSync(scripts[0], 'utf-8'); - expect(contents).to.contain('n=e,t=e.exports,function()'); - expect(contents).to.contain('document.getElementsByTagName("span")[0].innerHTML=`import from lodash ${i}`;'); + expect(contents).to.contain('r=u,e=u.exports,function()'); + expect(contents).to.contain('document.getElementsByTagName("span")[0].innerHTML=`import from lodash ${o}`'); }); }); }); diff --git a/packages/plugin-renderer-puppeteer/test/cases/build.default/build.default.spec.js b/packages/plugin-renderer-puppeteer/test/cases/build.default/build.default.spec.js index 82fe77ebc..c4449df2e 100644 --- a/packages/plugin-renderer-puppeteer/test/cases/build.default/build.default.spec.js +++ b/packages/plugin-renderer-puppeteer/test/cases/build.default/build.default.spec.js @@ -298,7 +298,7 @@ describe('Build Greenwood With: ', function() { expect(inlineScriptTag.textContent.replace('\n', '')).to // eslint-disable-next-line max-len - .contain('import"/lit-element.6ff69bae.js";document.getElementsByClassName("output-script-inline")[0].innerHTML="script tag module inline";//# sourceMappingURL='); + .contain('import"/lit-element.76be1f9d.js";document.getElementsByClassName("output-script-inline")[0].innerHTML="script tag module inline";//# sourceMappingURL=1635690801.3e40c728.js.map'); }); it('should have prerendered content from component', function() { diff --git a/www/components/banner/banner.js b/www/components/banner/banner.js index 25085d258..1638d9bde 100644 --- a/www/components/banner/banner.js +++ b/www/components/banner/banner.js @@ -2,7 +2,6 @@ import { css, html, LitElement, unsafeCSS } from 'lit'; import bannerCss from './banner.css?type=css'; import buttonCss from './button.css?type=css'; import './eve-button.js'; -import '@evergreen-wc/eve-container'; class Banner extends LitElement { constructor() { diff --git a/www/components/header/header.js b/www/components/header/header.js index 5a708a06e..08ac20552 100644 --- a/www/components/header/header.js +++ b/www/components/header/header.js @@ -1,7 +1,6 @@ import { css, html, LitElement, unsafeCSS } from 'lit'; import client from '@greenwood/plugin-graphql/src/core/client.js'; import MenuQuery from '@greenwood/plugin-graphql/src/queries/menu.gql'; -import '@evergreen-wc/eve-container'; import headerCss from './header.css?type=css'; import '../social-icons/social-icons.js'; diff --git a/yarn.lock b/yarn.lock index 30ff4b322..660a73a29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -306,19 +306,26 @@ dependencies: "@babel/types" "^7.13.0" -"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13": +"@babel/helper-module-imports@^7.12.1": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb" + integrity sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA== + dependencies: + "@babel/types" "^7.12.5" + +"@babel/helper-module-imports@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0" integrity sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g== dependencies: "@babel/types" "^7.12.13" -"@babel/helper-module-imports@^7.12.1": - version "7.12.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb" - integrity sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA== +"@babel/helper-module-imports@^7.18.6": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== dependencies: - "@babel/types" "^7.12.5" + "@babel/types" "^7.22.15" "@babel/helper-module-transforms@^7.12.1": version "7.12.1" @@ -1614,10 +1621,10 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== +"@jridgewell/source-map@^0.3.3": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" + integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== dependencies: "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" @@ -1627,7 +1634,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -3667,62 +3674,54 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@rollup/plugin-babel@^5.3.0": - version "5.3.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879" - integrity sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw== +"@rollup/plugin-babel@^6.0.4": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz#bd698e351fa9aa9619fcae780aea2a603d98e4c4" + integrity sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw== dependencies: - "@babel/helper-module-imports" "^7.10.4" - "@rollup/pluginutils" "^3.1.0" + "@babel/helper-module-imports" "^7.18.6" + "@rollup/pluginutils" "^5.0.1" -"@rollup/plugin-commonjs@^21.0.0": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.0.tgz#b9e4342855ea20b5528f4587b9a90f642196a502" - integrity sha512-XDQimjHl0kNotAV5lLo34XoygaI0teqiKGJ100B3iCU8+15YscJPeqk2KqkqD3NIe1H8ZTUo5lYjUFZyEgASTw== +"@rollup/plugin-commonjs@^25.0.0": + version "25.0.7" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz#145cec7589ad952171aeb6a585bbeabd0fd3b4cf" + integrity sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ== dependencies: - "@rollup/pluginutils" "^3.1.0" + "@rollup/pluginutils" "^5.0.1" commondir "^1.0.1" - estree-walker "^2.0.1" - glob "^7.1.6" - is-reference "^1.2.1" - magic-string "^0.25.7" - resolve "^1.17.0" + estree-walker "^2.0.2" + glob "^8.0.3" + is-reference "1.2.1" + magic-string "^0.30.3" -"@rollup/plugin-node-resolve@^13.0.0": - version "13.0.5" - resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.5.tgz#016abe58796a4ff544d6beac7818921e3d3777fc" - integrity sha512-mVaw6uxtvuGx/XCI4qBQXsDZJUfyx5vp39iE0J/7Hd6wDhEbjHr6aES7Nr9yWbuE0BY+oKp6N7Bq6jX5NCGNmQ== +"@rollup/plugin-node-resolve@^15.0.0": + version "15.2.3" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz#e5e0b059bd85ca57489492f295ce88c2d4b0daf9" + integrity sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ== dependencies: - "@rollup/pluginutils" "^3.1.0" - "@types/resolve" "1.17.1" - builtin-modules "^3.1.0" + "@rollup/pluginutils" "^5.0.1" + "@types/resolve" "1.20.2" deepmerge "^4.2.2" + is-builtin-module "^3.2.1" is-module "^1.0.0" - resolve "^1.19.0" - -"@rollup/plugin-replace@^2.3.4": - version "2.3.4" - resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz#7dd84c17755d62b509577f2db37eb524d7ca88ca" - integrity sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ== - dependencies: - "@rollup/pluginutils" "^3.1.0" - magic-string "^0.25.7" + resolve "^1.22.1" -"@rollup/plugin-terser@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-terser/-/plugin-terser-0.1.0.tgz#7530c0f11667637419d71820461646c418526041" - integrity sha512-N2KK+qUfHX2hBzVzM41UWGLrEmcjVC37spC8R3c9mt3oEDFKh3N2e12/lLp9aVSt86veR0TQiCNQXrm8C6aiUQ== +"@rollup/plugin-replace@^5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz#33d5653dce6d03cb24ef98bef7f6d25b57faefdf" + integrity sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ== dependencies: - terser "^5.15.1" + "@rollup/pluginutils" "^5.0.1" + magic-string "^0.30.3" -"@rollup/pluginutils@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" - integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== +"@rollup/plugin-terser@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz#15dffdb3f73f121aa4fbb37e7ca6be9aeea91962" + integrity sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A== dependencies: - "@types/estree" "0.0.39" - estree-walker "^1.0.1" - picomatch "^2.2.2" + serialize-javascript "^6.0.1" + smob "^1.0.0" + terser "^5.17.4" "@rollup/pluginutils@^4.0.0": version "4.2.1" @@ -3732,10 +3731,19 @@ estree-walker "^2.0.1" picomatch "^2.2.2" -"@rollup/stream@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@rollup/stream/-/stream-2.0.0.tgz#2ada818c2d042e37f63119d7bf8bbfc71792f641" - integrity sha512-HsCyY/phZMys1zFUYoYlnDJGG9zMmYFfEjDKNQa00CYgjeyGD4cLdO6KNIkBh61AWOZfOsTPuGtNmFCsjQOfFg== +"@rollup/pluginutils@^5.0.1": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0" + integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + +"@rollup/stream@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@rollup/stream/-/stream-3.0.1.tgz#485452d6f1016ac1b0513060f90ff02aaca3e1c0" + integrity sha512-wdzoakLc9UiPOFa1k17ukfEtvQ0p7JuNFvOZT1DhO5Z5CrTf71An01U9+v+aebYcaLCwy3tLwpCSUF7K7xVN0A== "@samverschueren/stream-to-observable@^0.3.0": version "0.3.1" @@ -3871,10 +3879,10 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== -"@types/estree@0.0.39": - version "0.0.39" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" - integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/express-serve-static-core@^4.17.18": version "4.17.18" @@ -4111,12 +4119,10 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== -"@types/resolve@1.17.1": - version "1.17.1" - resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" - integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== - dependencies: - "@types/node" "*" +"@types/resolve@1.20.2": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" + integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== "@types/retry@0.12.1": version "0.12.1" @@ -4502,7 +4508,7 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.1, acorn@^8.0.5, acorn@^8.5.0, acorn@^8.7.0: +acorn@^8.0.1, acorn@^8.0.5, acorn@^8.7.0: version "8.8.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== @@ -4512,6 +4518,11 @@ acorn@^8.4.1, acorn@^8.6.0, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.8.2: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + agent-base@4, agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -5531,11 +5542,6 @@ bufrw@^1.2.1: hexer "^1.5.0" xtend "^4.0.0" -builtin-modules@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" - integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== - builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -7615,16 +7621,11 @@ estraverse@^5.1.0, estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estree-walker@2.0.2, estree-walker@^2.0.1: +estree-walker@2.0.2, estree-walker@^2.0.1, estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== -estree-walker@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" - integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== - esutils@^2.0.2, esutils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -8757,7 +8758,7 @@ glob@7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.1, glob@^7.1.4, glob@^7.1.6: +glob@^7.1.1, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -9777,7 +9778,7 @@ is-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== -is-builtin-module@^3.1.0: +is-builtin-module@^3.1.0, is-builtin-module@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== @@ -10104,7 +10105,7 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-reference@^1.2.1: +is-reference@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== @@ -11150,12 +11151,12 @@ macos-release@^3.1.0: resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-3.2.0.tgz#dcee82b6a4932971b1538dbf6f3aabc4a903b613" integrity sha512-fSErXALFNsnowREYZ49XCdOHF8wOPWuFOGQrAhP7x5J/BqQv+B02cNsTykGpDgRVx43EKg++6ANmTaGTtW+hUA== -magic-string@^0.25.7: - version "0.25.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" - integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== +magic-string@^0.30.3: + version "0.30.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== dependencies: - sourcemap-codec "^1.4.4" + "@jridgewell/sourcemap-codec" "^1.4.15" make-dir@^1.0.0: version "1.3.0" @@ -14568,7 +14569,7 @@ resolve@^1.10.1: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.3.2: +resolve@^1.12.0, resolve@^1.14.2, resolve@^1.3.2: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -14692,10 +14693,10 @@ rollup-plugin-analyzer@^4.0.0: resolved "https://registry.yarnpkg.com/rollup-plugin-analyzer/-/rollup-plugin-analyzer-4.0.0.tgz#96b757ed64a098b59d72f085319e68cdd86d5798" integrity sha512-LL9GEt3bkXp6Wa19SNR5MWcvHNMvuTFYg+eYBZN2OIFhSWN+pEJUQXEKu5BsOeABob3x9PDaLKW7w5iOJnsESQ== -rollup@^2.58.0: - version "2.79.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" - integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== +rollup@^3.29.4: + version "3.29.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== optionalDependencies: fsevents "~2.3.2" @@ -14862,6 +14863,13 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" +serialize-javascript@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" + integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== + dependencies: + randombytes "^2.1.0" + serve-static@1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" @@ -15050,6 +15058,11 @@ smart-buffer@^4.1.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== +smob@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/smob/-/smob-1.4.1.tgz#66270e7df6a7527664816c5b577a23f17ba6f5b5" + integrity sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ== + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -15168,11 +15181,6 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -sourcemap-codec@^1.4.4: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== - space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" @@ -15887,13 +15895,13 @@ terminal-link@3.0.0, terminal-link@^3.0.0: ansi-escapes "^5.0.0" supports-hyperlinks "^2.2.0" -terser@^5.15.1: - version "5.16.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.0.tgz#29362c6f5506e71545c73b069ccd199bb28f7f54" - integrity sha512-KjTV81QKStSfwbNiwlBXfcgMcOloyuRdb62/iLFPGBcVNF4EXjhdYBhYHmbJpiBrVxZhDvltE11j+LBQUxEEJg== +terser@^5.17.4: + version "5.25.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.25.0.tgz#6579b4cca45b08bf0fdaa1a04605fd5860dfb2ac" + integrity sha512-we0I9SIsfvNUMP77zC9HG+MylwYYsGFSBG8qm+13oud2Yh+O104y614FRbyjpxys16jZwot72Fpi827YvGzuqg== dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" commander "^2.20.0" source-map-support "~0.5.20" From d7b0f2f3355cf4c52de2f8a3a8b16fee11ba8c05 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 9 Mar 2024 19:30:09 -0500 Subject: [PATCH 03/47] feature/discussion 1117 Isolation Mode (v1) (#1206) * isolation mode for SSR pages and API routes for greenwood serve * documentation for isolation mode option and global config test case * misc refactoring * set isolation mode to true for Lit renderer plugin * set isolation mode to true for Lit renderer plugin --- packages/cli/src/lib/execute-route-module.js | 5 +- .../lib/ssr-route-worker-isolation-mode.js | 14 ++++ packages/cli/src/lifecycles/config.js | 11 ++- packages/cli/src/lifecycles/graph.js | 50 ++++++----- packages/cli/src/lifecycles/serve.js | 83 +++++++++++++++++-- .../build.config.error-isolation.spec.js | 49 +++++++++++ .../greenwood.config.js | 3 + .../serve.default.api.spec.js | 2 +- .../serve.default.api/src/api/fragment.js | 2 + .../serve.default.ssr.spec.js | 2 +- .../serve.default.ssr/src/pages/users.js | 4 +- packages/plugin-renderer-lit/README.md | 2 + .../src/execute-route-module.js | 8 +- .../cases/serve.default/serve.default.spec.js | 2 +- .../cases/serve.default/src/pages/users.js | 1 + www/pages/docs/api-routes.md | 12 ++- www/pages/docs/configuration.md | 33 +++++++- www/pages/docs/server-rendering.md | 11 +++ 18 files changed, 259 insertions(+), 35 deletions(-) create mode 100644 packages/cli/src/lib/ssr-route-worker-isolation-mode.js create mode 100644 packages/cli/test/cases/build.config.error-isolation/build.config.error-isolation.spec.js create mode 100644 packages/cli/test/cases/build.config.error-isolation/greenwood.config.js diff --git a/packages/cli/src/lib/execute-route-module.js b/packages/cli/src/lib/execute-route-module.js index 483696fdc..1d3746ace 100644 --- a/packages/cli/src/lib/execute-route-module.js +++ b/packages/cli/src/lib/execute-route-module.js @@ -15,7 +15,7 @@ async function executeRouteModule({ moduleUrl, compilation, page = {}, prerender data.html = html; } else { const module = await import(moduleUrl).then(module => module); - const { prerender = false, getTemplate = null, getBody = null, getFrontmatter = null } = module; + const { prerender = false, getTemplate = null, getBody = null, getFrontmatter = null, isolation } = module; if (module.default) { const { html } = await renderToString(new URL(moduleUrl), false, request); @@ -35,7 +35,10 @@ async function executeRouteModule({ moduleUrl, compilation, page = {}, prerender data.frontmatter = await getFrontmatter(compilation, page); } + // TODO cant we get these from just pulling from the file during the graph phase? + // https://github.com/ProjectEvergreen/greenwood/issues/991 data.prerender = prerender; + data.isolation = isolation; } return data; diff --git a/packages/cli/src/lib/ssr-route-worker-isolation-mode.js b/packages/cli/src/lib/ssr-route-worker-isolation-mode.js new file mode 100644 index 000000000..831fa8e71 --- /dev/null +++ b/packages/cli/src/lib/ssr-route-worker-isolation-mode.js @@ -0,0 +1,14 @@ +// https://github.com/nodejs/modules/issues/307#issuecomment-858729422 +import { parentPort } from 'worker_threads'; + +async function executeModule({ routeModuleUrl, request, compilation }) { + const { handler } = await import(routeModuleUrl); + const response = await handler(request, compilation); + const html = await response.text(); + + parentPort.postMessage(html); +} + +parentPort.on('message', async (task) => { + await executeModule(task); +}); \ No newline at end of file diff --git a/packages/cli/src/lifecycles/config.js b/packages/cli/src/lifecycles/config.js index e9b9dd400..e15fcc998 100644 --- a/packages/cli/src/lifecycles/config.js +++ b/packages/cli/src/lifecycles/config.js @@ -50,6 +50,7 @@ const defaultConfig = { plugins: greenwoodPlugins, markdown: { plugins: [], settings: {} }, prerender: false, + isolation: false, pagesDirectory: 'pages', templatesDirectory: 'templates' }; @@ -76,7 +77,7 @@ const readAndMergeConfig = async() => { if (hasConfigFile) { const userCfgFile = (await import(configUrl)).default; - const { workspace, devServer, markdown, optimization, plugins, port, prerender, basePath, staticRouter, pagesDirectory, templatesDirectory, interpolateFrontmatter } = userCfgFile; + const { workspace, devServer, markdown, optimization, plugins, port, prerender, basePath, staticRouter, pagesDirectory, templatesDirectory, interpolateFrontmatter, isolation } = userCfgFile; // workspace validation if (workspace) { @@ -223,6 +224,14 @@ const readAndMergeConfig = async() => { customConfig.prerender = false; } + if (isolation !== undefined) { + if (typeof isolation === 'boolean') { + customConfig.isolation = isolation; + } else { + reject(`Error: greenwood.config.js isolation must be a boolean; true or false. Passed value was typeof: ${typeof staticRouter}`); + } + } + if (staticRouter !== undefined) { if (typeof staticRouter === 'boolean') { customConfig.staticRouter = staticRouter; diff --git a/packages/cli/src/lifecycles/graph.js b/packages/cli/src/lifecycles/graph.js index 40b95f7a9..d0ed0027e 100644 --- a/packages/cli/src/lifecycles/graph.js +++ b/packages/cli/src/lifecycles/graph.js @@ -22,7 +22,8 @@ const generateGraph = async (compilation) => { data: {}, imports: [], resources: [], - prerender: true + prerender: true, + isolation: false }]; const walkDirectoryForPages = async function(directory, pages = []) { @@ -49,6 +50,7 @@ const generateGraph = async (compilation) => { let customData = {}; let filePath; let prerender = true; + let isolation = false; /* * check if additional nested directories exist to correctly determine route (minus filename) @@ -131,6 +133,7 @@ const generateGraph = async (compilation) => { worker.on('message', async (result) => { prerender = result.prerender; + isolation = result.isolation ?? isolation; if (result.frontmatter) { result.frontmatter.imports = result.frontmatter.imports || []; @@ -202,6 +205,7 @@ const generateGraph = async (compilation) => { * title: a default value that can be used for * isSSR: if this is a server side route * prerednder: if this should be statically exported + * isolation: if this should be run in isolated mode */ pages.push({ data: customData || {}, @@ -222,7 +226,8 @@ const generateGraph = async (compilation) => { template, title, isSSR: !isStatic, - prerender + prerender, + isolation }); } } @@ -242,27 +247,34 @@ const generateGraph = async (compilation) => { apis = await walkDirectoryForApis(filenameUrlAsDir, apis); } else { const extension = filenameUrl.pathname.split('.').pop(); - const relativeApiPath = filenameUrl.pathname.replace(userWorkspace.pathname, '/'); - const route = `${basePath}${relativeApiPath.replace(`.${extension}`, '')}`; if (extension !== 'js') { console.warn(`${filenameUrl} is not a JavaScript file, skipping...`); - } else { - /* - * API Properties (per route) - *---------------------- - * filename: base filename of the page - * outputPath: the filename to write to when generating a build - * path: path to the file relative to the workspace - * route: URL route for a given page on outputFilePath - */ - apis.set(route, { - filename: filename, - outputPath: `/api/${filename}`, - path: relativeApiPath, - route - }); + return; } + + const relativeApiPath = filenameUrl.pathname.replace(userWorkspace.pathname, '/'); + const route = `${basePath}${relativeApiPath.replace(`.${extension}`, '')}`; + // TODO should this be run in isolation like SSR pages? + // https://github.com/ProjectEvergreen/greenwood/issues/991 + const { isolation } = await import(filenameUrl).then(module => module); + + /* + * API Properties (per route) + *---------------------- + * filename: base filename of the page + * outputPath: the filename to write to when generating a build + * path: path to the file relative to the workspace + * route: URL route for a given page on outputFilePath + * isolation: if this should be run in isolated mode + */ + apis.set(route, { + filename: filename, + outputPath: `/api/${filename}`, + path: relativeApiPath, + route, + isolation + }); } } diff --git a/packages/cli/src/lifecycles/serve.js b/packages/cli/src/lifecycles/serve.js index 7c94c29a8..1aaa2e8c4 100644 --- a/packages/cli/src/lifecycles/serve.js +++ b/packages/cli/src/lifecycles/serve.js @@ -2,9 +2,10 @@ import fs from 'fs/promises'; import { hashString } from '../lib/hashing-utils.js'; import Koa from 'koa'; import { koaBody } from 'koa-body'; -import { checkResourceExists, mergeResponse, transformKoaRequestIntoStandardRequest } from '../lib/resource-utils.js'; +import { checkResourceExists, mergeResponse, transformKoaRequestIntoStandardRequest, requestAsObject } from '../lib/resource-utils.js'; import { Readable } from 'stream'; import { ResourceInterface } from '../lib/resource-interface.js'; +import { Worker } from 'worker_threads'; async function getDevServer(compilation) { const app = new Koa(); @@ -282,6 +283,7 @@ async function getStaticServer(compilation, composable) { async function getHybridServer(compilation) { const { graph, manifest, context, config } = compilation; const { outputDir } = context; + const isolationMode = config.isolation; const app = await getStaticServer(compilation, true); app.use(koaBody()); @@ -294,17 +296,84 @@ async function getHybridServer(compilation) { const request = transformKoaRequestIntoStandardRequest(url, ctx.request); if (!config.prerender && matchingRoute.isSSR && !matchingRoute.prerender) { - const { handler } = await import(new URL(`./${matchingRoute.outputPath}`, outputDir)); - const response = await handler(request, compilation); + const entryPointUrl = new URL(`./${matchingRoute.outputPath}`, outputDir); + let html; + + if (matchingRoute.isolation || isolationMode) { + await new Promise(async (resolve, reject) => { + const worker = new Worker(new URL('../lib/ssr-route-worker-isolation-mode.js', import.meta.url)); + // TODO "faux" new Request here, a better way? + const request = await requestAsObject(new Request(url)); + + worker.on('message', async (result) => { + html = result; + + resolve(); + }); + worker.on('error', reject); + worker.on('exit', (code) => { + if (code !== 0) { + reject(new Error(`Worker stopped with exit code ${code}`)); + } + }); + + worker.postMessage({ + routeModuleUrl: entryPointUrl.href, + request, + compilation: JSON.stringify(compilation) + }); + }); + } else { + const { handler } = await import(entryPointUrl); + const response = await handler(request, compilation); - ctx.body = Readable.from(response.body); + html = Readable.from(response.body); + } + + ctx.body = html; ctx.set('Content-Type', 'text/html'); ctx.status = 200; } else if (isApiRoute) { const apiRoute = manifest.apis.get(url.pathname); - const { handler } = await import(new URL(`.${apiRoute.path}`, outputDir)); - const response = await handler(request); - const { body, status, headers, statusText } = response; + let body, status, headers, statusText; + + if (apiRoute.isolation || isolationMode) { + await new Promise(async (resolve, reject) => { + const worker = new Worker(new URL('../lib/api-route-worker.js', import.meta.url)); + // TODO "faux" new Request here, a better way? + const req = await requestAsObject(request); + + worker.on('message', async (result) => { + const responseAsObject = result; + + body = responseAsObject.body; + status = responseAsObject.status; + headers = new Headers(responseAsObject.headers); + statusText = responseAsObject.statusText; + + resolve(); + }); + worker.on('error', reject); + worker.on('exit', (code) => { + if (code !== 0) { + reject(new Error(`Worker stopped with exit code ${code}`)); + } + }); + + worker.postMessage({ + href: new URL(`.${apiRoute.path}`, outputDir).href, + request: req + }); + }); + } else { + const { handler } = await import(new URL(`.${apiRoute.path}`, outputDir)); + const response = await handler(request); + + body = response.body; + status = response.status; + headers = response.headers; + statusText = response.statusText; + } ctx.body = body ? Readable.from(body) : null; ctx.status = status; diff --git a/packages/cli/test/cases/build.config.error-isolation/build.config.error-isolation.spec.js b/packages/cli/test/cases/build.config.error-isolation/build.config.error-isolation.spec.js new file mode 100644 index 000000000..b262cc677 --- /dev/null +++ b/packages/cli/test/cases/build.config.error-isolation/build.config.error-isolation.spec.js @@ -0,0 +1,49 @@ +/* + * Use Case + * Run Greenwood build command with a bad value for isolation mode in a custom config. + * + * User Result + * Should throw an error. + * + * User Command + * greenwood build + * + * User Config + * { + * isolation: {} + * } + * + * User Workspace + * Greenwood default + */ +import chai from 'chai'; +import path from 'path'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Build Greenwood With: ', function() { + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(); + }); + + describe('Custom Configuration with a bad value for Isolation', function() { + it('should throw an error that isolation must be a boolean', function() { + try { + runner.setup(outputPath); + runner.runCommand(cliPath, 'build'); + } catch (err) { + expect(err).to.contain('Error: greenwood.config.js isolation must be a boolean; true or false. Passed value was typeof: object'); + } + }); + }); + +}); \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.error-isolation/greenwood.config.js b/packages/cli/test/cases/build.config.error-isolation/greenwood.config.js new file mode 100644 index 000000000..211201604 --- /dev/null +++ b/packages/cli/test/cases/build.config.error-isolation/greenwood.config.js @@ -0,0 +1,3 @@ +export default { + isolation: {} +}; \ No newline at end of file diff --git a/packages/cli/test/cases/serve.default.api/serve.default.api.spec.js b/packages/cli/test/cases/serve.default.api/serve.default.api.spec.js index 3baa2e383..979721912 100644 --- a/packages/cli/test/cases/serve.default.api/serve.default.api.spec.js +++ b/packages/cli/test/cases/serve.default.api/serve.default.api.spec.js @@ -14,7 +14,7 @@ * User Workspace * src/ * api/ - * fragment.js + * fragment.js (isolation mode) * greeting.js * missing.js * nothing.js diff --git a/packages/cli/test/cases/serve.default.api/src/api/fragment.js b/packages/cli/test/cases/serve.default.api/src/api/fragment.js index ab5a5722c..992d3dd91 100644 --- a/packages/cli/test/cases/serve.default.api/src/api/fragment.js +++ b/packages/cli/test/cases/serve.default.api/src/api/fragment.js @@ -1,5 +1,7 @@ import { renderFromHTML } from 'wc-compiler'; +export const isolation = true; + export async function handler(request) { const params = new URLSearchParams(request.url.slice(request.url.indexOf('?'))); const name = params.has('name') ? params.get('name') : 'World'; diff --git a/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js b/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js index 424896114..8093b069a 100644 --- a/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js +++ b/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js @@ -28,7 +28,7 @@ * index.js * index.js * post.js - * users.js + * users.js (isolation = true) * templates/ * app.html */ diff --git a/packages/cli/test/cases/serve.default.ssr/src/pages/users.js b/packages/cli/test/cases/serve.default.ssr/src/pages/users.js index f2eeafaf3..71a468f33 100644 --- a/packages/cli/test/cases/serve.default.ssr/src/pages/users.js +++ b/packages/cli/test/cases/serve.default.ssr/src/pages/users.js @@ -17,4 +17,6 @@ export default class UsersPage extends HTMLElement { ${html} `; } -} \ No newline at end of file +} + +export const isolation = true; \ No newline at end of file diff --git a/packages/plugin-renderer-lit/README.md b/packages/plugin-renderer-lit/README.md index 0bdae3e78..7fa2b6f2e 100644 --- a/packages/plugin-renderer-lit/README.md +++ b/packages/plugin-renderer-lit/README.md @@ -88,6 +88,8 @@ customElements.define('artists-page', ArtistsPage); export const tagName = 'artists-page'; ``` +> _By default, this plugin sets `isolation` mode to `true` for all SSR pages. See the [isolation configuration](https://www.greenwoodjs.io/docs/configuration/#isolation) docs for more information._ + ## Caveats There are a few considerations to take into account when using a `LitElement` as your page component: diff --git a/packages/plugin-renderer-lit/src/execute-route-module.js b/packages/plugin-renderer-lit/src/execute-route-module.js index 5e73f7505..10a7d0149 100644 --- a/packages/plugin-renderer-lit/src/execute-route-module.js +++ b/packages/plugin-renderer-lit/src/execute-route-module.js @@ -38,7 +38,13 @@ async function executeRouteModule({ moduleUrl, compilation, page, prerender, htm data.html = await getTemplateResultString(templateResult); } else { const module = await import(moduleUrl).then(module => module); - const { getTemplate = null, getBody = null, getFrontmatter = null } = module; + const { getTemplate = null, getBody = null, getFrontmatter = null, isolation = true } = module; + + // TODO cant we get these from just pulling from the file during the graph phase? + // https://github.com/ProjectEvergreen/greenwood/issues/991 + if (isolation) { + data.isolation = true; + } if (module.default && module.tagName) { const { tagName } = module; diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js index b36cf230d..2f776bc7c 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js +++ b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js @@ -19,7 +19,7 @@ * greeting.js * pages/ * artists.js - * users.js + * users.js (isolation = false) * templates/ * app.html */ diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/users.js b/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/users.js index f6fe81ebf..71a9515e5 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/users.js +++ b/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/users.js @@ -20,5 +20,6 @@ class UsersComponent extends LitElement { customElements.define('app-users', UsersComponent); +export const isolation = false; export const tagName = 'app-users'; export default UsersComponent; \ No newline at end of file diff --git a/www/pages/docs/api-routes.md b/www/pages/docs/api-routes.md index 4e248c192..b05f28d8e 100644 --- a/www/pages/docs/api-routes.md +++ b/www/pages/docs/api-routes.md @@ -75,4 +75,14 @@ export async function handler(request) { return new Response(html, { headers }); } -``` \ No newline at end of file +``` + +### Isolation + +To execute an API route in its own request context when running `greenwood serve`, you can export an `isolation` option from your page set to `true`. + +```js +export const isolation = true; +``` + +> For more information and how you can enable this for all pages, please see the [isolation configuration](/docs/configuration/#isolation) docs. \ No newline at end of file diff --git a/www/pages/docs/configuration.md b/www/pages/docs/configuration.md index 1575d5013..177cb1d47 100644 --- a/www/pages/docs/configuration.md +++ b/www/pages/docs/configuration.md @@ -31,7 +31,8 @@ export default { plugins: [], workspace: new URL('./src/', import.meta.url), pagesDirectory: 'pages', // e.g. src/pages - templatesDirectory: 'templates' // e.g. src/templates + templatesDirectory: 'templates', // e.g. src/templates + isolation: false }; ``` @@ -127,6 +128,36 @@ Lorum Ipsum. ``` +### Isolation Mode + +If running Greenwood as a server in production with the `greenwood serve` command, it may be desirable to isolate the server rendering of SSR pages and API routes from the global runtime (e.g. NodeJS) process. This is a common assumption for many Web Component libraries that may aim to more faithfully honor the browser's native specification on the server. + +Examples include: +- Custom Elements Registry - Per the spec, a custom element can only be defined once using `customElements.define`. +- DOM Shims - These often assume a globally unique runtime, and so issues can arise when these DOM globals are repeatedly loaded and initialized into the global space + +> See these discussions for more information +> - https://github.com/ProjectEvergreen/greenwood/discussions/1117 +> - https://github.com/ProjectEvergreen/wcc/discussions/145 + +As servers have to support multiple clients (as opposed to a browser tab only serving one client at a time), Greenwood offers an isolation mode that can be used to run SSR pages and API routes in their own context per request. + +#### Example + +To configure an entire project for this, simply set the flag in your _greenwood.config.js_ +```js +export default { + isolation: true // default value is false +}; +``` + +Optionally, you can opt-in on a per SSR page / API route basis by exporting an `isolation` option. +```js +// src/pages/products.js + +export const isolation = true; +``` + ### Markdown You can install and provide custom **unifiedjs** [presets](https://github.com/unifiedjs/unified#preset) and [plugins](https://github.com/unifiedjs/unified#plugin) to further customize and process your markdown past what [Greenwood does by default](https://github.com/ProjectEvergreen/greenwood/blob/release/0.10.0/packages/cli/src/transforms/transform.md.js#L68). After running an `npm install` you can provide their package names to Greenwood. diff --git a/www/pages/docs/server-rendering.md b/www/pages/docs/server-rendering.md index ca94e015e..411cc06fe 100644 --- a/www/pages/docs/server-rendering.md +++ b/www/pages/docs/server-rendering.md @@ -225,6 +225,17 @@ export const prerender = true; > You can enable this for all pages using the [prerender configuration](/docs/configuration/#prerender) option. +### Isolation + +To execute an SSR page in its own request context when running `greenwood serve`, you can export an `isolation` option from your page set to `true`. + +```js +export const isolation = true; +``` + +> For more information and how you can enable this for all pages, please see the [isolation configuration](/docs/configuration/#isolation) docs. + + ### Custom Imports > ⚠️ _This feature is experimental._ From 6f0ba0db50004544c50b3e19bffb293a20445957 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 9 Mar 2024 21:19:43 -0500 Subject: [PATCH 04/47] feature/issue 1197 Lit v3 upgrade and SSR fixes and enhancements (#1201) * initial upgrade for test cases * upgrade CLI and www to Lit v3 * revert static router test case change * all tests passing * patch escodegen for better ESM support * test for lit element hydration script * refactor rollup config * update Lit renderer README and apply conditional Lit script hydration * comments and TODOs cleanup * minor rebase patches * WCC v0.12.0 upgrade and removing patches directory * hydration default documentation and testing for Lit renderer plugin --- greenwood.config.js | 4 +- package.json | 2 +- packages/cli/package.json | 4 +- packages/cli/src/config/rollup.config.js | 36 +--- packages/cli/src/lib/templating-utils.js | 19 +- packages/cli/src/lifecycles/bundle.js | 23 ++ packages/cli/src/lifecycles/graph.js | 10 +- .../plugins/resource/plugin-standard-html.js | 1 + .../build.default.import-node-modules.spec.js | 5 +- .../package.json | 2 +- .../build.default.spa.spec.js | 3 +- .../test/cases/build.default.spa/package.json | 2 +- .../build.default.ssr-static-export.spec.js | 3 +- .../package.json | 2 +- .../develop.default/develop.default.spec.js | 5 + .../develop.default/import-map.snapshot.json | 4 - .../test/cases/develop.default/package.json | 2 +- .../cases/develop.ssr/develop.ssr.spec.js | 5 + .../cli/test/cases/develop.ssr/package.json | 2 +- .../package.json | 2 +- .../serve.default.ssr-static-export.spec.js | 3 +- .../test/cases/query-children/package.json | 2 +- .../query-children/query-children.spec.js | 5 + .../query-custom-frontmatter/package.json | 2 +- .../query-custom-frontmatter.spec.js | 5 + .../test/cases/query-graph/package.json | 2 +- .../cases/query-graph/query-graph.spec.js | 5 + .../test/cases/query-menu/package.json | 2 +- .../test/cases/query-menu/query-menu.spec.js | 5 + packages/plugin-import-jsx/package.json | 2 +- .../test/cases/lit/lit.spec.js | 4 +- .../test/cases/lit/package.json | 2 +- packages/plugin-renderer-lit/README.md | 100 +++++---- packages/plugin-renderer-lit/package.json | 7 +- .../src/execute-route-module.js | 44 ++-- packages/plugin-renderer-lit/src/index.js | 43 +++- .../build.prerender.getting-started.spec.js | 11 + .../package.json | 2 +- .../test/cases/serve.default/package.json | 2 +- .../cases/serve.default/serve.default.spec.js | 103 +++++++-- .../cases/serve.default/src/api/search.js | 41 ++++ .../serve.default/src/components/card.js | 82 +++++++ .../cases/serve.default/src/pages/artists.js | 1 + .../cases/serve.default/src/pages/users.js | 30 +-- .../cases/build.default/build.default.spec.js | 7 +- .../test/cases/build.default/package.json | 2 +- .../options.extend-config/src/scripts/main.ts | 3 +- www/package.json | 2 +- yarn.lock | 200 +++++++++++------- 49 files changed, 576 insertions(+), 279 deletions(-) create mode 100644 packages/plugin-renderer-lit/test/cases/serve.default/src/api/search.js create mode 100644 packages/plugin-renderer-lit/test/cases/serve.default/src/components/card.js diff --git a/greenwood.config.js b/greenwood.config.js index 73fc49abb..648fdc361 100644 --- a/greenwood.config.js +++ b/greenwood.config.js @@ -14,7 +14,9 @@ export default { interpolateFrontmatter: true, plugins: [ greenwoodPluginGraphQL(), - greenwoodPluginPolyfills(), + greenwoodPluginPolyfills({ + lit: true + }), greenwoodPluginPostCss(), greenwoodPluginImportJson(), greenwoodPluginImportCss(), diff --git a/package.json b/package.json index 71518641b..fe99893d8 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "lint": "ls-lint && yarn lint:js && yarn lint:ts && yarn lint:css" }, "resolutions": { - "lit": "^2.1.1" + "lit": "^3.1.0" }, "devDependencies": { "@ls-lint/ls-lint": "^1.10.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 95c5c400b..635220224 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -52,7 +52,7 @@ "remark-rehype": "^7.0.0", "rollup": "^3.29.4", "unified": "^9.2.0", - "wc-compiler": "~0.11.0" + "wc-compiler": "~0.12.0" }, "devDependencies": { "@babel/runtime": "^7.10.4", @@ -62,7 +62,7 @@ "@material/mwc-button": "^0.25.2", "@stencil/core": "^2.12.0", "@types/trusted-types": "^2.0.2", - "lit": "^2.0.0", + "lit": "^3.1.0", "lit-redux-router": "~0.20.0", "lodash-es": "^4.17.20", "postcss-nested": "^4.1.2", diff --git a/packages/cli/src/config/rollup.config.js b/packages/cli/src/config/rollup.config.js index dc0d582d6..7244a9284 100644 --- a/packages/cli/src/config/rollup.config.js +++ b/packages/cli/src/config/rollup.config.js @@ -6,30 +6,11 @@ import commonjs from '@rollup/plugin-commonjs'; 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', ''); } -// specifically to handle escodegen and other node modules -// using require for package.json or other json files -// https://github.com/estools/escodegen/issues/455 -function greenwoodJsonLoader() { - return { - name: 'greenwood-json-loader', - async load(id) { - const idUrl = new URL(`file://${cleanRollupId(id)}`); - const extension = idUrl.pathname.split('.').pop(); - - if (extension === 'json') { - const json = JSON.parse(await fs.promises.readFile(idUrl, 'utf-8')); - const contents = `export default ${JSON.stringify(json)}`; - - return contents; - } - } - }; -} - function greenwoodResourceLoader (compilation) { const resourcePlugins = compilation.config.plugins.filter((plugin) => { return plugin.type === 'resource'; @@ -386,9 +367,14 @@ const getRollupConfigForApis = async (compilation) => { chunkFileNames: '[name].[hash].js' }, plugins: [ - greenwoodJsonLoader(), greenwoodResourceLoader(compilation), - nodeResolve(), + // support node export conditions for API routes + // https://github.com/ProjectEvergreen/greenwood/issues/1118 + // https://github.com/rollup/plugins/issues/362#issuecomment-873448461 + nodeResolve({ + exportConditions: ['node'], + preferBuiltins: true + }), commonjs(), greenwoodImportMetaUrl(compilation) ] @@ -408,12 +394,12 @@ const getRollupConfigForSsr = async (compilation, input) => { chunkFileNames: '[name].[hash].js' }, plugins: [ - greenwoodJsonLoader(), greenwoodResourceLoader(compilation), - // TODO let this through for lit to enable nodeResolve({ preferBuiltins: true }) - // https://github.com/lit/lit/issues/449 + // support node export conditions for SSR pages // https://github.com/ProjectEvergreen/greenwood/issues/1118 + // https://github.com/rollup/plugins/issues/362#issuecomment-873448461 nodeResolve({ + exportConditions: ['node'], preferBuiltins: true }), commonjs(), diff --git a/packages/cli/src/lib/templating-utils.js b/packages/cli/src/lib/templating-utils.js index 9503e6aac..f9e83b771 100644 --- a/packages/cli/src/lib/templating-utils.js +++ b/packages/cli/src/lib/templating-utils.js @@ -1,7 +1,6 @@ import fs from 'fs/promises'; import htmlparser from 'node-html-parser'; import { checkResourceExists } from './resource-utils.js'; -import { getPackageJson } from './node-modules-utils.js'; async function getCustomPageTemplatesFromPlugins(contextPlugins, templateName) { const customTemplateLocations = []; @@ -177,7 +176,7 @@ async function getAppTemplate(pageTemplateContents, context, customImports = [], } async function getUserScripts (contents, compilation) { - const { context, config } = compilation; + const { config } = compilation; contents = contents.replace('', ` @@ -186,22 +185,6 @@ async function getUserScripts (contents, compilation) { `); - // TODO get rid of lit polyfills in core - // https://github.com/ProjectEvergreen/greenwood/issues/728 - // https://lit.dev/docs/tools/requirements/#polyfills - if (process.env.__GWD_COMMAND__ === 'build') { // eslint-disable-line no-underscore-dangle - const userPackageJson = await getPackageJson(context); - const dependencies = userPackageJson?.dependencies || {}; - const litPolyfill = dependencies && dependencies.lit - ? '\n' - : ''; - - contents = contents.replace('', ` - - ${litPolyfill} - `); - } - return contents; } diff --git a/packages/cli/src/lifecycles/bundle.js b/packages/cli/src/lifecycles/bundle.js index 4aad70159..625dd4a6d 100644 --- a/packages/cli/src/lifecycles/bundle.js +++ b/packages/cli/src/lifecycles/bundle.js @@ -7,6 +7,28 @@ import { checkResourceExists, mergeResponse, normalizePathnameForWindows } from import path from 'path'; import { rollup } from 'rollup'; +async function interceptPage(url, request, plugins, body) { + let response = new Response(body, { + headers: new Headers({ 'Content-Type': 'text/html' }) + }); + + for (const plugin of plugins) { + if (plugin.shouldIntercept && await plugin.shouldIntercept(url, request, response)) { + response = await plugin.intercept(url, request, response); + } + } + + return response; +} + +function getPluginInstances(compilation) { + return [...compilation.config.plugins] + .filter(plugin => plugin.type === 'resource' && plugin.name !== 'plugin-node-modules:resource') + .map((plugin) => { + return plugin.provider(compilation); + }); +} + async function emitResources(compilation) { const { outputDir } = compilation.context; const { resources, graph } = compilation; @@ -203,6 +225,7 @@ async function bundleSsrPages(compilation) { staticHtml = data.template ? data.template : await getPageTemplate(staticHtml, compilation.context, template, []); staticHtml = await getAppTemplate(staticHtml, compilation.context, imports, [], false, title); staticHtml = await getUserScripts(staticHtml, compilation); + staticHtml = await (await interceptPage(new URL(`http://localhost:8080${route}`), new Request(new URL(`http://localhost:8080${route}`)), getPluginInstances(compilation), staticHtml)).text(); staticHtml = await (await htmlOptimizer.optimize(new URL(`http://localhost:8080${route}`), new Response(staticHtml))).text(); staticHtml = staticHtml.replace(/[`\\$]/g, '\\$&'); // https://stackoverflow.com/a/75688937/417806 diff --git a/packages/cli/src/lifecycles/graph.js b/packages/cli/src/lifecycles/graph.js index d0ed0027e..57b53b7bc 100644 --- a/packages/cli/src/lifecycles/graph.js +++ b/packages/cli/src/lifecycles/graph.js @@ -51,6 +51,7 @@ const generateGraph = async (compilation) => { let filePath; let prerender = true; let isolation = false; + let hydration = false; /* * check if additional nested directories exist to correctly determine route (minus filename) @@ -132,8 +133,9 @@ const generateGraph = async (compilation) => { const request = await requestAsObject(new Request(filenameUrl)); worker.on('message', async (result) => { - prerender = result.prerender; + prerender = result.prerender ?? false; isolation = result.isolation ?? isolation; + hydration = result.hydration ?? hydration; if (result.frontmatter) { result.frontmatter.imports = result.frontmatter.imports || []; @@ -204,8 +206,9 @@ const generateGraph = async (compilation) => { * template: page template to use as a base for a generated component * title: a default value that can be used for * isSSR: if this is a server side route - * prerednder: if this should be statically exported + * prerender: if this should be statically exported * isolation: if this should be run in isolated mode + * hydration: if this page needs hydration support */ pages.push({ data: customData || {}, @@ -227,7 +230,8 @@ const generateGraph = async (compilation) => { title, isSSR: !isStatic, prerender, - isolation + isolation, + hydration }); } } diff --git a/packages/cli/src/plugins/resource/plugin-standard-html.js b/packages/cli/src/plugins/resource/plugin-standard-html.js index b64fcee64..000788cd0 100644 --- a/packages/cli/src/plugins/resource/plugin-standard-html.js +++ b/packages/cli/src/plugins/resource/plugin-standard-html.js @@ -115,6 +115,7 @@ class StandardHtmlResource extends ResourceInterface { if (result.template) { ssrTemplate = result.template; } + if (result.body) { ssrBody = result.body; } diff --git a/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js b/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js index 736d93df5..6f89c6466 100644 --- a/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js +++ b/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js @@ -217,9 +217,8 @@ describe('Build Greenwood With: ', function() { expect(mainScriptTags.length).to.be.equal(1); }); - // TODO clean up lit-polyfill as part of https://github.com/ProjectEvergreen/greenwood/issues/728 it('should have the total expected number of .js file in the output directory', async function() { - expect(await glob.promise(path.join(this.context.publicDir, '*.js'))).to.have.lengthOf(4); + expect(await glob.promise(path.join(this.context.publicDir, '*.js'))).to.have.lengthOf(3); }); it('should have the expected main.js file in the output directory', async function() { @@ -242,7 +241,7 @@ describe('Build Greenwood With: ', function() { const inlineScriptTag = Array.from(dom.window.document.querySelectorAll('head > script:not([src])')).filter(tag => !tag.getAttribute('data-gwd'))[0]; expect(inlineScriptTag.textContent.replace(/\n/g, '')).to - .equal('import"/116321042.262925e6.js";import"/lit-html.71ac31d8.js";//# sourceMappingURL=116321042.f667a8c7.js.map'); + .equal('import"/116321042.4f3171e3.js";import"/lit-html.31ea57aa.js";//# sourceMappingURL=116321042.69f46fc1.js.map'); }); }); diff --git a/packages/cli/test/cases/build.default.import-node-modules/package.json b/packages/cli/test/cases/build.default.import-node-modules/package.json index a280755e3..bf0c8c424 100644 --- a/packages/cli/test/cases/build.default.import-node-modules/package.json +++ b/packages/cli/test/cases/build.default.import-node-modules/package.json @@ -2,7 +2,7 @@ "name": "test-import-node-modules", "type": "module", "dependencies": { - "lit": "^2.0.0", + "lit": "^3.1.0", "lodash-es": "^4.17.20", "prismjs": "^1.21.0", "pwa-helpers": "^0.9.1", diff --git a/packages/cli/test/cases/build.default.spa/build.default.spa.spec.js b/packages/cli/test/cases/build.default.spa/build.default.spa.spec.js index 0905ab808..334895120 100644 --- a/packages/cli/test/cases/build.default.spa/build.default.spa.spec.js +++ b/packages/cli/test/cases/build.default.spa/build.default.spa.spec.js @@ -235,8 +235,7 @@ describe('Build Greenwood With: ', function() { // one for the footer.js // one for index.js // one for lit element bundle - // TODO clean up lit-polyfill as part of https://github.com/ProjectEvergreen/greenwood/issues/728 - expect(jsFiles.length).to.be.equal(6); + expect(jsFiles.length).to.be.equal(5); }); it('should have custom tag in the <head>', function() { diff --git a/packages/cli/test/cases/build.default.spa/package.json b/packages/cli/test/cases/build.default.spa/package.json index bc59f2ff5..e8f6bae6e 100644 --- a/packages/cli/test/cases/build.default.spa/package.json +++ b/packages/cli/test/cases/build.default.spa/package.json @@ -1,7 +1,7 @@ { "type": "module", "dependencies": { - "lit": "^2.0.0", + "lit": "^3.1.0", "lit-redux-router": "~0.20.0", "pwa-helpers": "^0.9.1", "redux": "^4.0.5", diff --git a/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js b/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js index 71582d099..566348c5c 100644 --- a/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js +++ b/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js @@ -147,11 +147,10 @@ describe('Build Greenwood With: ', function() { expect(styles.length).to.equal(1); }); - // TODO clean up lit-polyfill as part of https://github.com/ProjectEvergreen/greenwood/issues/728 it('should have four script tags', function() { const scripts = Array.from(dom.window.document.querySelectorAll('head > script')).filter(tag => !tag.getAttribute('data-gwd')); - expect(scripts.length).to.equal(4); + expect(scripts.length).to.equal(3); }); it('should have expected SSR content from the non module script tag', function() { diff --git a/packages/cli/test/cases/build.default.ssr-static-export/package.json b/packages/cli/test/cases/build.default.ssr-static-export/package.json index 2dee3e768..0b8c2d82a 100644 --- a/packages/cli/test/cases/build.default.ssr-static-export/package.json +++ b/packages/cli/test/cases/build.default.ssr-static-export/package.json @@ -1,6 +1,6 @@ { "type": "module", "dependencies": { - "lit": "^2.0.0" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/cli/test/cases/develop.default/develop.default.spec.js b/packages/cli/test/cases/develop.default/develop.default.spec.js index 8c3cf16e2..3958267f5 100644 --- a/packages/cli/test/cases/develop.default/develop.default.spec.js +++ b/packages/cli/test/cases/develop.default/develop.default.spec.js @@ -101,6 +101,10 @@ describe('Develop Greenwood With: ', function() { `${process.cwd()}/node_modules/lit/package.json`, `${outputPath}/node_modules/lit/` ); + const litSsrPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); const litElement = await getDependencyFiles( `${process.cwd()}/node_modules/lit-element/*.js`, `${outputPath}/node_modules/lit-element/` @@ -379,6 +383,7 @@ describe('Develop Greenwood With: ', function() { ...getSetupFiles(outputPath), ...lit, ...litPackageJson, + ...litSsrPackageJson, ...litDirectives, ...litDecorators, ...litElementPackageJson, diff --git a/packages/cli/test/cases/develop.default/import-map.snapshot.json b/packages/cli/test/cases/develop.default/import-map.snapshot.json index ad256ef39..4e5f60629 100644 --- a/packages/cli/test/cases/develop.default/import-map.snapshot.json +++ b/packages/cli/test/cases/develop.default/import-map.snapshot.json @@ -235,10 +235,6 @@ "lit-html/directives/unsafe-svg.js": "/node_modules/lit-html/directives/unsafe-svg.js", "lit/directives/unsafe-svg.js": "/node_modules/lit/directives/unsafe-svg.js", "lit-html/directives/until.js": "/node_modules/lit-html/directives/until.js", - "lit-element/experimental-hydrate-support.js": "/node_modules/lit-element/experimental-hydrate-support.js", - "lit/experimental-hydrate-support.js": "/node_modules/lit/experimental-hydrate-support.js", - "lit-html/experimental-hydrate.js": "/node_modules/lit-html/experimental-hydrate.js", - "lit/experimental-hydrate.js": "/node_modules/lit/experimental-hydrate.js", "lit/html.js": "/node_modules/lit/html.js", "lit/polyfill-support.js": "/node_modules/lit/polyfill-support.js", "lit-html/static.js": "/node_modules/lit-html/static.js", diff --git a/packages/cli/test/cases/develop.default/package.json b/packages/cli/test/cases/develop.default/package.json index cb0f93aca..b3fda5ee7 100644 --- a/packages/cli/test/cases/develop.default/package.json +++ b/packages/cli/test/cases/develop.default/package.json @@ -8,6 +8,6 @@ "@material/mwc-button": "^0.25.2", "@stencil/core": "^2.12.0", "@types/trusted-types": "^2.0.2", - "lit": "^2.0.0" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/cli/test/cases/develop.ssr/develop.ssr.spec.js b/packages/cli/test/cases/develop.ssr/develop.ssr.spec.js index 18ecbccbf..9a4628250 100644 --- a/packages/cli/test/cases/develop.ssr/develop.ssr.spec.js +++ b/packages/cli/test/cases/develop.ssr/develop.ssr.spec.js @@ -67,6 +67,10 @@ describe('Develop Greenwood With: ', function() { `${process.cwd()}/node_modules/lit/package.json`, `${outputPath}/node_modules/lit/` ); + const litSsrPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); const litElement = await getDependencyFiles( `${process.cwd()}/node_modules/lit-element/*.js`, `${outputPath}/node_modules/lit-element/` @@ -114,6 +118,7 @@ describe('Develop Greenwood With: ', function() { ...getSetupFiles(outputPath), ...lit, ...litPackageJson, + ...litSsrPackageJson, ...litDirectives, ...litDecorators, ...litElementPackageJson, diff --git a/packages/cli/test/cases/develop.ssr/package.json b/packages/cli/test/cases/develop.ssr/package.json index 2dee3e768..0b8c2d82a 100644 --- a/packages/cli/test/cases/develop.ssr/package.json +++ b/packages/cli/test/cases/develop.ssr/package.json @@ -1,6 +1,6 @@ { "type": "module", "dependencies": { - "lit": "^2.0.0" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/cli/test/cases/serve.default.ssr-static-export/package.json b/packages/cli/test/cases/serve.default.ssr-static-export/package.json index 2dee3e768..0b8c2d82a 100644 --- a/packages/cli/test/cases/serve.default.ssr-static-export/package.json +++ b/packages/cli/test/cases/serve.default.ssr-static-export/package.json @@ -1,6 +1,6 @@ { "type": "module", "dependencies": { - "lit": "^2.0.0" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/cli/test/cases/serve.default.ssr-static-export/serve.default.ssr-static-export.spec.js b/packages/cli/test/cases/serve.default.ssr-static-export/serve.default.ssr-static-export.spec.js index 26fa97766..117e6ed62 100644 --- a/packages/cli/test/cases/serve.default.ssr-static-export/serve.default.ssr-static-export.spec.js +++ b/packages/cli/test/cases/serve.default.ssr-static-export/serve.default.ssr-static-export.spec.js @@ -175,11 +175,10 @@ describe('Serve Greenwood With: ', function() { expect(styles.length).to.equal(1); }); - // TODO clean up lit-polyfill as part of https://github.com/ProjectEvergreen/greenwood/issues/728 it('should have four script tags', function() { const scripts = Array.from(dom.window.document.querySelectorAll('head script')).filter(tag => !tag.getAttribute('data-gwd')); - expect(scripts.length).to.equal(4); + expect(scripts.length).to.equal(3); }); it('should have expected SSR content from the non module script tag', function() { diff --git a/packages/plugin-graphql/test/cases/query-children/package.json b/packages/plugin-graphql/test/cases/query-children/package.json index 9b235502e..225becaa2 100644 --- a/packages/plugin-graphql/test/cases/query-children/package.json +++ b/packages/plugin-graphql/test/cases/query-children/package.json @@ -2,6 +2,6 @@ "name": "plugin-graphql-test-children-query", "type": "module", "dependencies": { - "lit": "^2.0.0" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/plugin-graphql/test/cases/query-children/query-children.spec.js b/packages/plugin-graphql/test/cases/query-children/query-children.spec.js index 65df87110..20c5793f8 100644 --- a/packages/plugin-graphql/test/cases/query-children/query-children.spec.js +++ b/packages/plugin-graphql/test/cases/query-children/query-children.spec.js @@ -77,6 +77,10 @@ describe('Build Greenwood With: ', function() { `${process.cwd()}/node_modules/lit/package.json`, `${outputPath}/node_modules/lit/` ); + const litSsrPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); const litElement = await getDependencyFiles( `${process.cwd()}/node_modules/lit-element/*.js`, `${outputPath}/node_modules/lit-element/` @@ -126,6 +130,7 @@ describe('Build Greenwood With: ', function() { ...greenwoodGraphqlQueryLibs, ...lit, ...litPackageJson, + ...litSsrPackageJson, ...litDirectives, ...litDecorators, ...litElementPackageJson, diff --git a/packages/plugin-graphql/test/cases/query-custom-frontmatter/package.json b/packages/plugin-graphql/test/cases/query-custom-frontmatter/package.json index 83511be89..e9e9ceab1 100644 --- a/packages/plugin-graphql/test/cases/query-custom-frontmatter/package.json +++ b/packages/plugin-graphql/test/cases/query-custom-frontmatter/package.json @@ -2,6 +2,6 @@ "name": "plugin-graphql-test-query-custom-frontmatter", "type": "module", "dependencies": { - "lit": "^2.0.0" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/plugin-graphql/test/cases/query-custom-frontmatter/query-custom-frontmatter.spec.js b/packages/plugin-graphql/test/cases/query-custom-frontmatter/query-custom-frontmatter.spec.js index c4a3a620e..967f4de86 100644 --- a/packages/plugin-graphql/test/cases/query-custom-frontmatter/query-custom-frontmatter.spec.js +++ b/packages/plugin-graphql/test/cases/query-custom-frontmatter/query-custom-frontmatter.spec.js @@ -79,6 +79,10 @@ describe('Build Greenwood With: ', function() { `${process.cwd()}/node_modules/lit/package.json`, `${outputPath}/node_modules/lit/` ); + const litSsrPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); const litElement = await getDependencyFiles( `${process.cwd()}/node_modules/lit-element/*.js`, `${outputPath}/node_modules/lit-element/` @@ -128,6 +132,7 @@ describe('Build Greenwood With: ', function() { ...greenwoodGraphqlQueryLibs, ...lit, ...litPackageJson, + ...litSsrPackageJson, ...litDirectives, ...litDecorators, ...litElementPackageJson, diff --git a/packages/plugin-graphql/test/cases/query-graph/package.json b/packages/plugin-graphql/test/cases/query-graph/package.json index 5a64a7a74..27178da74 100644 --- a/packages/plugin-graphql/test/cases/query-graph/package.json +++ b/packages/plugin-graphql/test/cases/query-graph/package.json @@ -2,6 +2,6 @@ "name": "plugin-graphql-test-graph-query", "type": "module", "dependencies": { - "lit": "^2.0.0" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/plugin-graphql/test/cases/query-graph/query-graph.spec.js b/packages/plugin-graphql/test/cases/query-graph/query-graph.spec.js index 43d20a5d0..11209a801 100644 --- a/packages/plugin-graphql/test/cases/query-graph/query-graph.spec.js +++ b/packages/plugin-graphql/test/cases/query-graph/query-graph.spec.js @@ -75,6 +75,10 @@ describe('Build Greenwood With: ', function() { `${process.cwd()}/node_modules/lit/package.json`, `${outputPath}/node_modules/lit/` ); + const litSsrPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); const litElement = await getDependencyFiles( `${process.cwd()}/node_modules/lit-element/*.js`, `${outputPath}/node_modules/lit-element/` @@ -124,6 +128,7 @@ describe('Build Greenwood With: ', function() { ...greenwoodGraphqlQueryLibs, ...lit, ...litPackageJson, + ...litSsrPackageJson, ...litDirectives, ...litDecorators, ...litElementPackageJson, diff --git a/packages/plugin-graphql/test/cases/query-menu/package.json b/packages/plugin-graphql/test/cases/query-menu/package.json index cf01e2bba..3d07f5b69 100644 --- a/packages/plugin-graphql/test/cases/query-menu/package.json +++ b/packages/plugin-graphql/test/cases/query-menu/package.json @@ -2,6 +2,6 @@ "name": "plugin-graphql-test-menu-query", "type": "module", "dependencies": { - "lit": "^2.0.0" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/plugin-graphql/test/cases/query-menu/query-menu.spec.js b/packages/plugin-graphql/test/cases/query-menu/query-menu.spec.js index ffe69c7f6..0d3df4ca6 100644 --- a/packages/plugin-graphql/test/cases/query-menu/query-menu.spec.js +++ b/packages/plugin-graphql/test/cases/query-menu/query-menu.spec.js @@ -77,6 +77,10 @@ describe('Build Greenwood With: ', async function() { `${process.cwd()}/node_modules/lit/package.json`, `${outputPath}/node_modules/lit/` ); + const litSsrPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); const litElement = await getDependencyFiles( `${process.cwd()}/node_modules/lit-element/*.js`, `${outputPath}/node_modules/lit-element/` @@ -126,6 +130,7 @@ describe('Build Greenwood With: ', async function() { ...greenwoodGraphqlQueryLibs, ...lit, ...litPackageJson, + ...litSsrPackageJson, ...litDirectives, ...litDecorators, ...litElementPackageJson, diff --git a/packages/plugin-import-jsx/package.json b/packages/plugin-import-jsx/package.json index 78f751188..7ef5f9900 100644 --- a/packages/plugin-import-jsx/package.json +++ b/packages/plugin-import-jsx/package.json @@ -27,7 +27,7 @@ "@greenwood/cli": "^0.28.0-alpha.4" }, "dependencies": { - "wc-compiler": "~0.11.0" + "wc-compiler": "~0.12.0" }, "devDependencies": { "@greenwood/cli": "^0.29.4" diff --git a/packages/plugin-polyfills/test/cases/lit/lit.spec.js b/packages/plugin-polyfills/test/cases/lit/lit.spec.js index 13f39990c..2ba1855f5 100644 --- a/packages/plugin-polyfills/test/cases/lit/lit.spec.js +++ b/packages/plugin-polyfills/test/cases/lit/lit.spec.js @@ -139,15 +139,13 @@ describe('Build Greenwood With: ', function() { dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, 'index.html')); }); - // TODO need to reconcile how this lit polyfilling is working since the CLI already adds it? - // related to https://github.com/ProjectEvergreen/greenwood/issues/728 it('should have one <script> tag for lit polyfills loaded in the <head> tag', function() { const scriptTags = dom.window.document.querySelectorAll('head > script'); const polyfillScriptTags = Array.prototype.slice.call(scriptTags).filter(script => { return script.src.indexOf('polyfill-support') >= 0; }); - expect(polyfillScriptTags.length).to.be.equal(2); + expect(polyfillScriptTags.length).to.be.equal(1); }); it('should have the expected lit polyfill files in the output directory', function() { diff --git a/packages/plugin-polyfills/test/cases/lit/package.json b/packages/plugin-polyfills/test/cases/lit/package.json index a18b66ac7..f1dc1522d 100644 --- a/packages/plugin-polyfills/test/cases/lit/package.json +++ b/packages/plugin-polyfills/test/cases/lit/package.json @@ -2,6 +2,6 @@ "name": "plugin-polyfills-lit-spec", "type": "module", "dependencies": { - "lit": "^2.0.0" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/plugin-renderer-lit/README.md b/packages/plugin-renderer-lit/README.md index 7fa2b6f2e..e5928e9d1 100644 --- a/packages/plugin-renderer-lit/README.md +++ b/packages/plugin-renderer-lit/README.md @@ -2,7 +2,7 @@ ## Overview -A Greenwood plugin for using [**Lit**'s SSR capabilities](https://github.com/lit/lit/tree/main/packages/labs/ssr) as a custom server-side renderer. Although support is experimental at this time, this plugin also gives the ability to statically render entire pages and templates (instead of puppeteer) to output completely static sites. +A Greenwood plugin for using [**Lit**'s SSR capabilities](https://github.com/lit/lit/tree/main/packages/labs/ssr) as a custom server-side renderer. Although support is experimental at this time, this plugin also gives the ability to statically render entire pages and templates to output completely static sites. _We are still actively working on SSR features and enhancements for Greenwood [as part of our 1.0 release](https://github.com/ProjectEvergreen/greenwood/issues?q=is%3Aissue+is%3Aopen+label%3Assr+milestone%3A1.0) so please feel free to test it out and report your feedback._ 🙏 @@ -11,7 +11,7 @@ _We are still actively working on SSR features and enhancements for Greenwood [a ## Prerequisite -This packages depends on the Lit package as a `peerDependency`. This means you must have Lit already installed in your project. You can install anything following the `2.x` release line. +This packages depends on the Lit package as a `peerDependency`. This means you must have Lit already installed in your project. You can install anything following the `3.x` release line. ```sh # npm @@ -33,7 +33,18 @@ npm install @greenwood/plugin-renderer-lit --save-dev yarn add @greenwood/plugin-renderer-lit --dev ``` +## Caveats + +1. Please familiarize yourself with some of the [caveats](https://lit.dev/docs/ssr/overview/#library-status) called out in the Lit docs, like: + - Lit SSR [**only** renders into declarative shadow roots](https://github.com/lit/lit/issues/3080#issuecomment-1165158794), so you will have to keep browser support and polyfill usage in mind. + - At this time, `LitElement` does not support `async` work. You can follow along with this issue [in the Lit repo](https://github.com/lit/lit/issues/2469). +1. Lit only supports templates on the server side for HTML only generated content, thus Greenwood's `getBody` API must be used. We would love for [server only components](https://github.com/lit/lit/issues/2469#issuecomment-1759583861) to be a thing though! +1. Full hydration support is not available yet. See [this Greenwood issue](https://github.com/ProjectEvergreen/greenwood/issues/880) to follow along when it will land + +> See [this repo](https://github.com/thescientist13/greenwood-lit-ssr) for a full demo of isomorphic Lit SSR with SSR pages and API routes deployed to Vercel serverless functions. + ## Usage + Add this plugin to your _greenwood.config.js_. ```javascript @@ -48,48 +59,33 @@ export default { } ``` -Now, you can write some [SSR routes](/docs/server-rendering/) using Lit including all the [available APIs](docs/server-rendering/#api). The below example uses the standard [SimpleGreeting](https://lit.dev/playground/) component from the Lit docs by also using a LitElement as the `default export`! -```js -import { html, LitElement } from 'lit'; -import './path/to/greeting.js'; - -export default class ArtistsPage extends LitElement { +Now, you can author [SSR pages](/docs/server-rendering/) using Lit templates and components using Greenwood's [`getBody` API](https://www.greenwoodjs.io/docs/server-rendering/#usage). The below is an example of generating a template of LitElement based `<app-card>` web components. - constructor() { - super(); - this.artists = [{ /* ... */ }]; - } - - render() { - const { artists } = this; - - return html` - ${ - artists.map((artist) => { - const { id, name, imageUrl } = artist; - - return html` - <a href="/artists/${id}" target="_blank"> - <simple-greeting .name="${name}"></simple-greeting> - </a> - - <img src="${imageUrl}" loading="lazy"/> - - <br/> - `; - }) - } - `; - } +```js +// src/pages/products.js +import { html } from 'lit'; +import '../components/card.js'; + +export async function getBody() { + const products = await getProducts(); + + return html` + ${ + products.map((product, idx) => { + const { title, thumbnail } = product; + + return html` + <app-card + title="${idx + 1}) ${title}" + thumbnail="${thumbnail}" + ></app-card> + `; + }) + } + `; } - -// for now these are needed for the Lit specific implementations -customElements.define('artists-page', ArtistsPage); -export const tagName = 'artists-page'; ``` -> _By default, this plugin sets `isolation` mode to `true` for all SSR pages. See the [isolation configuration](https://www.greenwoodjs.io/docs/configuration/#isolation) docs for more information._ - ## Caveats There are a few considerations to take into account when using a `LitElement` as your page component: @@ -97,11 +93,29 @@ There are a few considerations to take into account when using a `LitElement` as - Depending on your use case, SSR bundling may break due to bundle chunking and code splitting by Rollup, which we are [hoping to correct ASAP](https://github.com/ProjectEvergreen/greenwood/issues/1118). - At this time, `LitElement` does [not support `async` work](https://lit.dev/docs/ssr/overview/#library-status) which makes data fetching in pages a bit of challenge. You can follow along with this issue [in the Lit repo](https://github.com/lit/lit/issues/2469). -> _You can see a work (in progress) demo of using Lit SSR (with Serverless!) [here](https://github.com/thescientist13/greenwood-demo-adapter-vercel-lit/)._ - ## Options -### Prerender (experimental) +### Isolation Mode + +By default, this plugin sets `isolation` mode to `true` for all SSR pages. If you want to override this, just export an `isolation` const. + +```js +// src/pages/products.js +export const isolation = false; +``` + +> _See the [isolation configuration](https://www.greenwoodjs.io/docs/configuration/#isolation) docs for more information._ + +### Hydration + +In order for server-rendered components to become interactive on the client side, Lit's [client-side hydration script](https://lit.dev/docs/ssr/client-usage/#loading-@lit-labsssr-clientlit-element-hydrate-support.js) must be included on the page. This setting is `true` by default, but if you want to turn it off, you can `export` the **hydration** option from your page with a value of `false`. + +```js +// src/pages/products.js +export const hydration = false; // disable Lit hydration scripts for this page +``` + +### Prerender The plugin provides a setting that can be used to override Greenwood's [default _prerender_](/docs/configuration/#prerender) implementation which uses [WCC](https://github.com/ProjectEvergreen/wcc), to use Lit instead. diff --git a/packages/plugin-renderer-lit/package.json b/packages/plugin-renderer-lit/package.json index 4db6a46d6..1738aa2c2 100644 --- a/packages/plugin-renderer-lit/package.json +++ b/packages/plugin-renderer-lit/package.json @@ -25,13 +25,14 @@ }, "peerDependencies": { "@greenwood/cli": "^0.22.1", - "lit": "^2.1.1" + "lit": "^3.1.0" }, "dependencies": { - "@lit-labs/ssr": "^2.0.1" + "@lit-labs/ssr": "^3.2.0", + "@lit-labs/ssr-client": "^1.1.6" }, "devDependencies": { "@greenwood/cli": "^0.29.4", - "lit": "^2.1.1" + "lit": "^3.1.0" } } diff --git a/packages/plugin-renderer-lit/src/execute-route-module.js b/packages/plugin-renderer-lit/src/execute-route-module.js index 10a7d0149..4ef510635 100644 --- a/packages/plugin-renderer-lit/src/execute-route-module.js +++ b/packages/plugin-renderer-lit/src/execute-route-module.js @@ -1,30 +1,15 @@ -// this needs to come first -import { render } from '@lit-labs/ssr/lib/render-with-global-dom-shim.js'; -import { Buffer } from 'buffer'; +import { render } from '@lit-labs/ssr'; +import { collectResult } from '@lit-labs/ssr/lib/render-result.js'; import { html } from 'lit'; import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; -import { Readable } from 'stream'; - -async function streamToString (stream) { - const chunks = []; - - for await (let chunk of stream) { - chunks.push(Buffer.from(chunk)); - } - - return Buffer.concat(chunks).toString('utf-8'); -} - -async function getTemplateResultString(template) { - return await streamToString(Readable.from(render(template))); -} async function executeRouteModule({ moduleUrl, compilation, page, prerender, htmlContents, scripts }) { const data = { template: null, body: null, frontmatter: null, - html: null + html: null, + hydration: false }; // prerender static content @@ -35,10 +20,10 @@ async function executeRouteModule({ moduleUrl, compilation, page, prerender, htm const templateResult = html`${unsafeHTML(htmlContents)}`; - data.html = await getTemplateResultString(templateResult); + data.html = await collectResult(render(templateResult)); } else { const module = await import(moduleUrl).then(module => module); - const { getTemplate = null, getBody = null, getFrontmatter = null, isolation = true } = module; + const { getTemplate = null, getBody = null, getFrontmatter = null, isolation = true, hydration = true } = module; // TODO cant we get these from just pulling from the file during the graph phase? // https://github.com/ProjectEvergreen/greenwood/issues/991 @@ -46,23 +31,20 @@ async function executeRouteModule({ moduleUrl, compilation, page, prerender, htm data.isolation = true; } - if (module.default && module.tagName) { - const { tagName } = module; - const templateResult = html` - ${unsafeHTML(`<${tagName}></${tagName}>`)} - `; + if (hydration) { + data.hydration = true; + } - data.body = await getTemplateResultString(templateResult); - } else if (getBody) { - const templateResult = await getBody(compilation, page); + if (getBody) { + const templateResult = await getBody(compilation, page, data.pageData); - data.body = await getTemplateResultString(templateResult); + data.body = await collectResult(render(templateResult)); } if (getTemplate) { const templateResult = await getTemplate(compilation, page); - data.template = await getTemplateResultString(templateResult); + data.template = await collectResult(render(templateResult)); } if (getFrontmatter) { diff --git a/packages/plugin-renderer-lit/src/index.js b/packages/plugin-renderer-lit/src/index.js index e4e190be0..4f33502bd 100755 --- a/packages/plugin-renderer-lit/src/index.js +++ b/packages/plugin-renderer-lit/src/index.js @@ -1,14 +1,51 @@ +import { ResourceInterface } from '@greenwood/cli/src/lib/resource-interface.js'; + +class LitHydrationResource extends ResourceInterface { + constructor(compilation, options) { + super(compilation, options); + } + + async shouldIntercept(url) { + const { pathname } = url; + const matchingRoute = this.compilation.graph.find((node) => node.route === pathname) || {}; + + return matchingRoute.isSSR && matchingRoute.hydration; + } + + async intercept(url, request, response) { + let body = await response.text(); + + // TODO would be nice not have to manually set module-shim + // when we drop support for import-map shim - https://github.com/ProjectEvergreen/greenwood/pull/1115 + const type = process.env.__GWD_COMMAND__ === 'develop' // eslint-disable-line no-underscore-dangle + ? 'module-shim' + : 'module'; + + // this needs to come first before any userland code + body = body.replace('<head>', ` + <head> + <script type="${type}" src="/node_modules/@lit-labs/ssr-client/lit-element-hydrate-support.js"></script> + `); + + return new Response(body); + } +} + const greenwoodPluginRendererLit = (options = {}) => { - return { + return [{ type: 'renderer', - name: 'plugin-renderer-lit', + name: 'plugin-renderer-lit:renderer', provider: () => { return { executeModuleUrl: new URL('./execute-route-module.js', import.meta.url), prerender: options.prerender }; } - }; + }, { + type: 'resource', + name: 'plugin-renderer-lit:resource', + provider: (compilation, options) => new LitHydrationResource(compilation, options) + }]; }; export { diff --git a/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/build.prerender.getting-started.spec.js b/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/build.prerender.getting-started.spec.js index adced6d2c..34ac418e6 100644 --- a/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/build.prerender.getting-started.spec.js +++ b/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/build.prerender.getting-started.spec.js @@ -96,6 +96,10 @@ describe('Build Greenwood With Custom Lit Renderer for SSG prerendering: ', func `${process.cwd()}/node_modules/lit-html/*.js`, `${outputPath}/node_modules/lit-html/` ); + const litHtmlNode = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/node/*.js`, + `${outputPath}/node_modules/lit-html/node/` + ); const litHtmlPackageJson = await getDependencyFiles( `${process.cwd()}/node_modules/lit-html/package.json`, `${outputPath}/node_modules/lit-html/` @@ -116,6 +120,11 @@ describe('Build Greenwood With Custom Lit Renderer for SSG prerendering: ', func `${process.cwd()}/node_modules/@lit/reactive-element/package.json`, `${outputPath}/node_modules/@lit/reactive-element/` ); + const litReactiveElementNode = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/node/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/node/` + ); + // lit-html/node/directives/unsafe-html.js const litHtmlSourceMap = await getDependencyFiles( `${process.cwd()}/node_modules/lit-html/lit-html.js.map`, `${outputPath}/node_modules/lit-html/` @@ -136,11 +145,13 @@ describe('Build Greenwood With Custom Lit Renderer for SSG prerendering: ', func ...litElementDecorators, ...litHtmlPackageJson, ...litHtml, + ...litHtmlNode, ...litHtmlDirectives, ...trustedTypes, ...litReactiveElement, ...litReactiveElementDecorators, ...litReactiveElementPackageJson, + ...litReactiveElementNode, ...litHtmlSourceMap ]); runner.runCommand(cliPath, 'build'); diff --git a/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/package.json b/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/package.json index fe421bf5b..c5116ac18 100644 --- a/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/package.json +++ b/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/package.json @@ -2,6 +2,6 @@ "name": "plugin-prerender-lit-build-prerender-getting-started", "type": "module", "dependencies": { - "lit": "^2.1.1" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/package.json b/packages/plugin-renderer-lit/test/cases/serve.default/package.json index 15b06e848..bacd51937 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/package.json +++ b/packages/plugin-renderer-lit/test/cases/serve.default/package.json @@ -2,6 +2,6 @@ "name": "plugin-prerender-lit-build-default", "type": "module", "dependencies": { - "lit": "^2.1.1" + "lit": "^3.1.0" } } \ No newline at end of file diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js index 2f776bc7c..5219f773b 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js +++ b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js @@ -13,8 +13,10 @@ * * User Workspace * src/ + * api/ + * search.js * components/ - * counter.js + * card.js * footer.js * greeting.js * pages/ @@ -82,6 +84,14 @@ describe('Serve Greenwood With: ', function() { `${process.cwd()}/node_modules/lit-html/*.js`, `${outputPath}/node_modules/lit-html/` ); + const litHtmlNode = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/node/*.js`, + `${outputPath}/node_modules/lit-html/node/` + ); + const litHtmlNodeDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/node/directives/*.js`, + `${outputPath}/node_modules/lit-html/node/directives/` + ); const litHtmlPackageJson = await getDependencyFiles( `${process.cwd()}/node_modules/lit-html/package.json`, `${outputPath}/node_modules/lit-html/` @@ -100,6 +110,10 @@ describe('Serve Greenwood With: ', function() { `${process.cwd()}/node_modules/@lit/reactive-element/*.js`, `${outputPath}/node_modules/@lit/reactive-element/` ); + const litReactiveElementNode = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/node/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/node/` + ); const litReactiveElementDecorators = await getDependencyFiles( `${process.cwd()}/node_modules/@lit/reactive-element/decorators/*.js`, `${outputPath}/node_modules/@lit/reactive-element/decorators/` @@ -108,6 +122,14 @@ describe('Serve Greenwood With: ', function() { `${process.cwd()}/node_modules/@lit/reactive-element/package.json`, `${outputPath}/node_modules/@lit/reactive-element/` ); + const litSsrElementHydrationSupport = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-client/lit-element-hydrate-support.js`, + `${outputPath}/node_modules/@lit-labs/ssr-client/` + ); + const litSsrHtmlHydrationSupport = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-client/lib/*.js`, + `${outputPath}/node_modules/@lit-labs/ssr-client/lib/` + ); runner.setup(outputPath, [ ...getSetupFiles(outputPath), @@ -120,11 +142,16 @@ describe('Serve Greenwood With: ', function() { ...litElementDecorators, ...litHtmlPackageJson, ...litHtml, + ...litHtmlNode, ...litHtmlDirectives, + ...litHtmlNodeDirectives, ...trustedTypes, ...litReactiveElement, + ...litReactiveElementNode, ...litReactiveElementDecorators, - ...litReactiveElementPackageJson + ...litReactiveElementPackageJson, + ...litSsrElementHydrationSupport, + ...litSsrHtmlHydrationSupport ]); runner.runCommand(cliPath, 'build'); @@ -160,7 +187,7 @@ describe('Serve Greenwood With: ', function() { usersPageDom = new JSDOM(usersPageHtml); }); - describe('Serve command with HTML route response using getBody, getTemplate and getFrontmatter', function() { + describe('Serve command with HTML route response using getBody, getTemplate and getFrontmatter for the artists page', function() { it('should return a 200 status', function() { expect(response.status).to.equal(200); @@ -184,15 +211,6 @@ describe('Serve Greenwood With: ', function() { expect(styles.length).to.equal(1); }); - // TODO this should be managed via a plugin, not in core - // https://github.com/ProjectEvergreen/greenwood/issues/728 - it('should have one <script> tag in the <head> for lit polyfills', function() { - const scripts = Array.from(dom.window.document.querySelectorAll('head > script')).filter(tag => !tag.getAttribute('data-gwd')); - - expect(scripts.length).to.equal(1); - expect(scripts[0].getAttribute('src').startsWith('/polyfill-support')).to.equal(true); - }); - it('should have the expected number of <tr> tags of content', function() { const rows = dom.window.document.querySelectorAll('body > table tr'); @@ -239,9 +257,16 @@ describe('Serve Greenwood With: ', function() { expect(aboutPageGraphData.data.author).to.equal('Project Evergreen'); expect(aboutPageGraphData.data.date).to.equal('01-01-2021'); }); + + it('should not have the expected lit hydration script in the <head>', function() { + const scripts = Array.from(dom.window.document.querySelectorAll('head script')) + .filter((script) => script.getAttribute('src')?.indexOf('lit-element-hydrate-support') >= 0); + + expect(scripts.length).to.equal(0); + }); }); - describe('Serve command with HTML route response using LitElement as default export', function() { + describe('Serve command with HTML route response using LitElement as a getPage export with an <app-footer> component for the users page', function() { it('the response body should be valid HTML from JSDOM', function(done) { expect(usersPageDom).to.not.be.undefined; done(); @@ -258,6 +283,58 @@ describe('Serve Greenwood With: ', function() { it('should have the expected <app-footer> content in the <body>', function() { expect(usersPageHtml).to.contain('<footer class="footer">'); }); + + it('should have the expected lit hydration script in the <head>', function() { + const scripts = Array.from(usersPageDom.window.document.querySelectorAll('head script')) + .filter((script) => script.getAttribute('src')?.indexOf('lit-element-hydrate-support') >= 0); + + expect(scripts.length).to.equal(1); + }); + }); + + describe('Serve command with API route server rendering LitElement <app-card> components as an HTML response', function() { + const term = 'Analog'; + let resp; + let html; + let dom; + + before(async function() { + resp = await fetch(`${hostname}/api/search`, { + method: 'POST', + body: new URLSearchParams({ term }).toString(), + headers: new Headers({ + 'content-type': 'application/x-www-form-urlencoded' + }) + }); + html = await resp.text(); + dom = new JSDOM(html); + }); + + it('should have a response status of 200', function(done) { + expect(resp.status).to.equal(200); + + done(); + }); + + it('should have a Content-Type header of text/html', function(done) { + const type = response.headers.get('Content-Type'); + + expect(type).to.equal('text/html'); + + done(); + }); + + it('should have the expected number of <app-card> components for a single search result', function(done) { + const cards = dom.window.document.querySelectorAll('app-card template[shadowrootmode="open"]'); + const cardDom = new JSDOM(cards[0].innerHTML); + + expect(cards.length).to.equal(1); + // TODO this should be real data (see issue with static in card.js) + expect(cardDom.window.document.querySelectorAll('h3')[0].textContent).to.equal('Foo'); + expect(cardDom.window.document.querySelectorAll('img')[0].getAttribute('src')).to.equal('bar.png'); + + done(); + }); }); }); diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/src/api/search.js b/packages/plugin-renderer-lit/test/cases/serve.default/src/api/search.js new file mode 100644 index 000000000..3000cc333 --- /dev/null +++ b/packages/plugin-renderer-lit/test/cases/serve.default/src/api/search.js @@ -0,0 +1,41 @@ +import { render } from '@lit-labs/ssr'; +import { collectResultSync } from '@lit-labs/ssr/lib/render-result.js'; +import fs from 'fs'; +import { html } from 'lit'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import '../components/card.js'; + +export async function handler(request) { + const artists = JSON.parse(fs.readFileSync(new URL('../../artists.json', import.meta.url), 'utf-8')); + const formData = await request.formData(); + const term = formData.has('term') ? formData.get('term') : ''; + const filteredArtists = artists.filter((artist) => { + return term !== '' && artist.name.toLowerCase().includes(term.toLowerCase()); + }); + let body = ''; + + if (filteredArtists.length === 0) { + body = 'No results found.'; + } else { + body = collectResultSync(render(html` + ${ + unsafeHTML(filteredArtists.map((item, idx) => { + const { name, imageUrl } = item; + + return ` + <app-card + title="${idx + 1}) ${name}" + thumbnail="${imageUrl}" + ></app-card> + `; + }).join('')) + } + `)); + } + + return new Response(body, { + headers: new Headers({ + 'Content-Type': 'text/html' + }) + }); +} \ No newline at end of file diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/src/components/card.js b/packages/plugin-renderer-lit/test/cases/serve.default/src/components/card.js new file mode 100644 index 000000000..32fb3e205 --- /dev/null +++ b/packages/plugin-renderer-lit/test/cases/serve.default/src/components/card.js @@ -0,0 +1,82 @@ +import { LitElement, html } from 'lit'; + +export default class Card extends LitElement { + // TODO we have a clash on acorn version? + // or this issue - https://github.com/ProjectEvergreen/greenwood/issues/1183 + // static properties = { + // title: '', + // thumbnail: '' + // }; + // static styles = css` + // div { + // display: flex; + // flex-direction: column; + // align-items: center; + // gap: 0.5rem; + // border: 1px solid #818181; + // width: fit-content; + // border-radius: 10px; + // padding: 2rem 1rem; + // height: 680px; + // justify-content: space-between; + // background-color: #fff; + // overflow-x: hidden; + // } + // button { + // background: var(--color-accent); + // color: var(--color-white); + // padding: 1rem 2rem; + // border: 0; + // font-size: 1rem; + // border-radius: 5px; + // cursor: pointer; + // } + // img { + // max-width: 500px; + // min-width: 500px; + // width: 100%; + // } + // h3 { + // font-size: 1.85rem; + // } + + // @media(max-width: 768px) { + // img { + // max-width: 300px; + // min-width: 300px; + // } + // div { + // height: 500px; + // } + // } + // `; + + constructor() { + super(); + + this.title; + this.thumbnail; + } + + selectItem() { + alert(`selected item is => ${this.title}!`); + } + + render() { + const { title = 'Foo', thumbnail = 'bar.png' } = this; + + if (!title && !thumbnail) { + return; + } + + return html` + <div> + <h3>${title}</h3> + <img src="${thumbnail}" alt="${title}" loading="lazy" width="100%"> + <button @click="${this.selectItem}">View Item Details</button> + </div> + `; + } +} + +customElements.define('app-card', Card); \ No newline at end of file diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/artists.js b/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/artists.js index f6a90b6f2..58596191f 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/artists.js +++ b/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/artists.js @@ -77,6 +77,7 @@ async function getFrontmatter(compilation, { route }) { }; } +export const hydration = false; export { getTemplate, getBody, diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/users.js b/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/users.js index 71a9515e5..47b662b67 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/users.js +++ b/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/users.js @@ -1,25 +1,15 @@ import fs from 'fs'; -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import '../components/footer.js'; -class UsersComponent extends LitElement { - - constructor() { - super(); - this.users = JSON.parse(fs.readFileSync(new URL('../../artists.json', import.meta.url), 'utf-8')); - } - - render() { - return html` - <h1>Users Page</h1> - <div id="users">${this.users.length}</div> - <app-footer></app-footer> - `; - } -} +export const isolation = false; -customElements.define('app-users', UsersComponent); +export async function getBody() { + const users = JSON.parse(fs.readFileSync(new URL('../../artists.json', import.meta.url), 'utf-8')); -export const isolation = false; -export const tagName = 'app-users'; -export default UsersComponent; \ No newline at end of file + return html` + <h1>Users Page</h1> + <div id="users">${users.length}</div> + <app-footer></app-footer> + `; +} \ No newline at end of file diff --git a/packages/plugin-renderer-puppeteer/test/cases/build.default/build.default.spec.js b/packages/plugin-renderer-puppeteer/test/cases/build.default/build.default.spec.js index c4449df2e..e6876fd31 100644 --- a/packages/plugin-renderer-puppeteer/test/cases/build.default/build.default.spec.js +++ b/packages/plugin-renderer-puppeteer/test/cases/build.default/build.default.spec.js @@ -68,6 +68,10 @@ describe('Build Greenwood With: ', function() { `${process.cwd()}/node_modules/lit/package.json`, `${outputPath}/node_modules/lit/` ); + const litSsrPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); const litElement = await getDependencyFiles( `${process.cwd()}/node_modules/lit-element/*.js`, `${outputPath}/node_modules/lit-element/` @@ -187,6 +191,7 @@ describe('Build Greenwood With: ', function() { ...symbolLibsPackageJson, ...lit, ...litPackageJson, + ...litSsrPackageJson, ...litDirectives, ...litDecorators, ...litElementPackageJson, @@ -298,7 +303,7 @@ describe('Build Greenwood With: ', function() { expect(inlineScriptTag.textContent.replace('\n', '')).to // eslint-disable-next-line max-len - .contain('import"/lit-element.76be1f9d.js";document.getElementsByClassName("output-script-inline")[0].innerHTML="script tag module inline";//# sourceMappingURL=1635690801.3e40c728.js.map'); + .contain('import"/lit-element.6eb76f27.js";document.getElementsByClassName("output-script-inline")[0].innerHTML="script tag module inline";//# sourceMappingURL=1635690801.4bc08000.js.map'); }); it('should have prerendered content from <app-header> component', function() { diff --git a/packages/plugin-renderer-puppeteer/test/cases/build.default/package.json b/packages/plugin-renderer-puppeteer/test/cases/build.default/package.json index 9348db6f7..42687993c 100644 --- a/packages/plugin-renderer-puppeteer/test/cases/build.default/package.json +++ b/packages/plugin-renderer-puppeteer/test/cases/build.default/package.json @@ -2,7 +2,7 @@ "name": "test-renderer-puppeteer", "type": "module", "dependencies": { - "lit": "^2.0.0", + "lit": "^3.1.0", "lodash-es": "^4.17.20", "prismjs": "^1.21.0", "pwa-helpers": "^0.9.1", diff --git a/packages/plugin-typescript/test/cases/options.extend-config/src/scripts/main.ts b/packages/plugin-typescript/test/cases/options.extend-config/src/scripts/main.ts index cddcb1a4e..b4e387822 100644 --- a/packages/plugin-typescript/test/cases/options.extend-config/src/scripts/main.ts +++ b/packages/plugin-typescript/test/cases/options.extend-config/src/scripts/main.ts @@ -1,4 +1,5 @@ -import { html, css, LitElement, customElement, property } from 'lit-element'; +import { html, css, LitElement } from 'lit-element'; +import { customElement, property } from 'lit/decorators.js'; import { TemplateResult } from 'lit-html'; @customElement('app-greeting') diff --git a/www/package.json b/www/package.json index e3183f30e..1731541bf 100644 --- a/www/package.json +++ b/www/package.json @@ -10,7 +10,7 @@ "dependencies": { "@evergreen-wc/eve-button": "^0.1.1", "@evergreen-wc/eve-container": "^0.1.1", - "lit": "^2.1.1", + "lit": "^3.1.0", "prismjs": "^1.21.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 660a73a29..e09df1dad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2393,28 +2393,36 @@ "@lion/core" "0.19.0" singleton-manager "1.4.2" -"@lit-labs/ssr-client@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@lit-labs/ssr-client/-/ssr-client-1.0.0.tgz#9d5a437f575469023500b7eff3bd27632197f720" - integrity sha512-bYnX1uhE7oO/579xRoAdAHnngROQHj93bqCUqw/L4vvRyBWBIeeshoM3OjPX0DFdeJIvZy/1x29t9iaNvTB5bg== +"@lit-labs/ssr-client@^1.1.4", "@lit-labs/ssr-client@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@lit-labs/ssr-client/-/ssr-client-1.1.6.tgz#d7aa2d68b18317057e193d18a8d81a2e347004f5" + integrity sha512-MHOHSDGmlum66PP2a6ATLHHwTFvYl+s7R4NYBp8E1NqHDA5UEuEIA1o/E4NoDinc3Ow56yz9Cvh89DvFDif5Pg== dependencies: - "@lit/reactive-element" "^1.0.0" - lit "^2.0.0" - lit-html "^2.0.0" + "@lit/reactive-element" "^1.6.1 || ^2.0.0" + lit "^2.7.0 || ^3.0.0" + lit-html "^2.7.0 || ^3.0.0" -"@lit-labs/ssr@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@lit-labs/ssr/-/ssr-2.0.1.tgz#18e5aa000b9937aeaa4325998e98a6f1c1a90b13" - integrity sha512-WVBWmGj8NmuJLDu/Y812zsro4kzh03ZVzTTIinFlDIrSawZcPi8ZOCDIxtsXcNLYkYhmBNOsEb9nDtF9opdXnA== - dependencies: - "@lit-labs/ssr-client" "^1.0.0" - "@lit/reactive-element" "^1.1.0" - lit "^2.1.0" - lit-element "^3.1.0" - lit-html "^2.1.0" - node-fetch "^2.6.0" - parse5 "^6.0.1" - resolve "^1.10.1" +"@lit-labs/ssr-dom-shim@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz#d693d972974a354034454ec1317eb6afd0b00312" + integrity sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g== + +"@lit-labs/ssr@^3.2.0": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@lit-labs/ssr/-/ssr-3.2.1.tgz#ee6230776387b02844c8d8d4a3084c9534f76898" + integrity sha512-y8LHFdRFYLXJ44kddCjeaKM/NVqcz1Z0TQHic7Siawxr07d4YZKYtghqnX1bbe2Oa8+qFW7NKFv54CIB7BzrZQ== + dependencies: + "@lit-labs/ssr-client" "^1.1.4" + "@lit-labs/ssr-dom-shim" "^1.1.2" + "@lit/reactive-element" "^1.6.1 || ^2.0.0" + "@parse5/tools" "^0.3.0" + "@types/node" "^16.0.0" + enhanced-resolve "^5.10.0" + lit "^3.1.0" + lit-element "^3.3.0 || ^4.0.0" + lit-html "^3.1.0" + node-fetch "^3.2.8" + parse5 "^7.1.1" "@lit/reactive-element@1.0.0-rc.4": version "1.0.0-rc.4" @@ -2426,10 +2434,12 @@ resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.0.0.tgz#7b6e6a85709cda0370c47e425ac2f3b553696a4b" integrity sha512-Kpgenb8UNFsKCsFhggiVvUkCbcFQSd6N8hffYEEGjz27/4rw3cTSsmP9t3q1EHOAsdum60Wo64HvuZDFpEwexA== -"@lit/reactive-element@^1.1.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.2.0.tgz#c62444a0e3d3f8d3a6875ad56f867279aa89fa88" - integrity sha512-7i/Fz8enAQ2AN5DyJ2i2AFERufjP6x1NjuHoNgDyJkjjHxEoo8kVyyHxu1A9YyeShlksjt5FvpvENBDuivQHLA== +"@lit/reactive-element@^1.6.1 || ^2.0.0", "@lit/reactive-element@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-2.0.3.tgz#29d7d4ee8d9b00804be957cc6937577eb4d3db63" + integrity sha512-e067EuTNNgOHm1tZcc0Ia7TCzD/9ZpoPegHKgesrGK6pSDRGkGDAQbYuQclqLPIoJ9eC8Kb9mYtGryWcM5AywA== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.1.2" "@ls-lint/ls-lint@^1.10.0": version "1.10.0" @@ -3600,6 +3610,13 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.15.1.tgz#3d745996b2bd11095b515515fd3d68d46092a02d" integrity sha512-n8Kur1/CZlYG32YCEj30CoUqA8R7UyDVZzoEU6SDP+13+kXDT2kFVu6MpcnEUTyGP3i058ID6Qjp5h6IJxdPPQ== +"@parse5/tools@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@parse5/tools/-/tools-0.3.0.tgz#4cac601408065a84c31a88431b02f9f69f92434a" + integrity sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg== + dependencies: + parse5 "^7.0.0" + "@pnpm/config.env-replace@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c" @@ -3621,6 +3638,22 @@ "@pnpm/network.ca-file" "^1.0.1" config-chain "^1.1.11" +"@projectevergreen/acorn-jsx-esm@~0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@projectevergreen/acorn-jsx-esm/-/acorn-jsx-esm-0.1.0.tgz#9870be988907048b5b432425d6157b84f8e4fdef" + integrity sha512-ZBSkr0e2M4ylq74dTGHSkWI2dF3Mz8zwBLyzIXZMftecKDADcsCTj7bWltVgtdl8Rh4+bmY1jNWUw7AlSV/r7A== + +"@projectevergreen/escodegen-esm@~0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@projectevergreen/escodegen-esm/-/escodegen-esm-0.1.0.tgz#c9259d51178d4be948dd19e561ce5e2a16948a0f" + integrity sha512-LM9FFffsXPHiOFt78K3bgF8kO8Fx+qluAPy9jP3H4lvCFE+2nbwQM4cWdOpVik++rHf4pkDA7FxDPWWATyBABg== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionalDependencies: + source-map "~0.6.1" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -4089,6 +4122,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.54.tgz#a737488631aca3ec7bd9f6229d77f1079e444793" integrity sha512-c8Lm7+hXdSPmWH4B9z/P/xIXhFK3mCQin4yCYMd2p1qpMG5AfgyJuYZ+3q2dT7qLiMMMGMd5dnkFpdqJARlvtQ== +"@types/node@^16.0.0": + version "16.18.76" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.76.tgz#deecdf6b71b9ef45ceda63fec7220ddf2354d7a1" + integrity sha512-/GsO2uv1Z6R42lBr59dtem56gVF/yHKQaScggwU+gLU6DXE25sDmOar4c4IfWb3h+X/7OYZznPOFk7oGF3jQSA== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -4508,15 +4546,10 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.1, acorn@^8.0.5, acorn@^8.7.0: - version "8.8.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" - integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== - -acorn@^8.4.1, acorn@^8.6.0, acorn@^8.9.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.0.1, acorn@^8.0.5, acorn@^8.4.1, acorn@^8.6.0, acorn@^8.7.0, acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== acorn@^8.8.2: version "8.11.2" @@ -7304,6 +7337,14 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" +enhanced-resolve@^5.10.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + entities@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -7319,6 +7360,11 @@ entities@^2.2.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@3.0.0, env-paths@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-3.0.0.tgz#2f1e89c2f6dbd3408e1b1711dd82d62e317f58da" @@ -8931,7 +8977,7 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.1.3, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.3, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -9823,13 +9869,6 @@ is-core-module@^2.13.0, is-core-module@^2.5.0: dependencies: has "^1.0.3" -is-core-module@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" - integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== - dependencies: - has "^1.0.3" - is-core-module@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" @@ -10770,25 +10809,19 @@ listr@0.14.3: p-map "^2.0.0" rxjs "^6.3.3" -lit-element@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.1.1.tgz#562d5ccbc8ba0c01d8ba4a0ac3576263167d2ccb" - integrity sha512-14ClnMAU8EXnzC+M2/KDd3SFmNUn1QUw1+GxWkEMwGV3iaH8ObunMlO5svzvaWlkSV0WlxJCi40NGnDVJ2XZKQ== - dependencies: - "@lit/reactive-element" "^1.1.0" - lit-html "^2.1.0" - -lit-html@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.0.0.tgz#ba6779269c382e66d7403a96ed99516ccc3d658b" - integrity sha512-tJsCapCmc0vtLj6harqd6HfCxnlt/RSkgowtz4SC9dFE3nSL38Tb33I5HMDiyJsRjQZRTgpVsahrnDrR9wg27w== +"lit-element@^3.3.0 || ^4.0.0", lit-element@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-4.0.3.tgz#18239840a7c1a6a6e09c6ed3b5cd3db0512ebf15" + integrity sha512-2vhidmC7gGLfnVx41P8UZpzyS0Fb8wYhS5RCm16cMW3oERO0Khd3EsKwtRpOnttuByI5rURjT2dfoA7NlInCNw== dependencies: - "@types/trusted-types" "^2.0.2" + "@lit-labs/ssr-dom-shim" "^1.1.2" + "@lit/reactive-element" "^2.0.0" + lit-html "^3.1.0" -lit-html@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.1.1.tgz#f4da485798a0d967514d31730d387350fafb79f7" - integrity sha512-E4BImK6lopAYanJpvcGaAG8kQFF1ccIulPu2BRNZI7acFB6i4ujjjsnaPVFT1j/4lD9r8GKih0Y8d7/LH8SeyQ== +"lit-html@^2.7.0 || ^3.0.0", lit-html@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-3.1.1.tgz#50c2e74a2074f85fc9816676ac11cf0c96f257c3" + integrity sha512-x/EwfGk2D/f4odSFM40hcGumzqoKv0/SUh6fBO+1Ragez81APrcAMPo1jIrCDd9Sn+Z4CT867HWKViByvkDZUA== dependencies: "@types/trusted-types" "^2.0.2" @@ -10799,14 +10832,14 @@ lit-redux-router@~0.20.0: dependencies: regexparam "^2.0.0" -lit@^2.0.0, lit@^2.0.0-rc.2, lit@^2.0.2, lit@^2.1.0, lit@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/lit/-/lit-2.1.1.tgz#65f43abca945988f696391f762c645ba51966b0b" - integrity sha512-yqDqf36IhXwOxIQSFqCMgpfvDCRdxLCLZl7m/+tO5C9W/OBHUj17qZpiMBT35v97QMVKcKEi1KZ3hZRyTwBNsQ== +lit@^2.0.0, lit@^2.0.0-rc.2, lit@^2.0.2, "lit@^2.7.0 || ^3.0.0", lit@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lit/-/lit-3.1.1.tgz#49340c8875019a777cc83904f75a2bf7764617dc" + integrity sha512-hF1y4K58+Gqrz+aAPS0DNBwPqPrg6P04DuWK52eMkt/SM9Qe9keWLcFgRcEKOLuDlRZlDsDbNL37Vr7ew1VCuw== dependencies: - "@lit/reactive-element" "^1.1.0" - lit-element "^3.1.0" - lit-html "^2.1.0" + "@lit/reactive-element" "^2.0.0" + lit-element "^4.0.0" + lit-html "^3.1.0" livereload-js@^3.1.0: version "3.3.1" @@ -12117,14 +12150,14 @@ node-fetch@2.6.12, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" -node-fetch@2.6.7, node-fetch@^2.5.0, node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@2.6.7, node-fetch@^2.5.0, node-fetch@^2.6.1: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" -node-fetch@^3.0.0, node-fetch@^3.1.1, node-fetch@^3.3.1: +node-fetch@^3.0.0, node-fetch@^3.1.1, node-fetch@^3.2.8, node-fetch@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== @@ -12988,6 +13021,13 @@ parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^7.0.0, parse5@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + parseurl@^1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -14560,15 +14600,6 @@ resolve@^1.10.0: dependencies: path-parse "^1.0.6" -resolve@^1.10.1: - version "1.21.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.21.0.tgz#b51adc97f3472e6a5cf4444d34bc9d6b9037591f" - integrity sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA== - dependencies: - is-core-module "^2.8.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - resolve@^1.12.0, resolve@^1.14.2, resolve@^1.3.2: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -15800,6 +15831,11 @@ tabtab@3.0.2: mkdirp "^0.5.1" untildify "^3.0.3" +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + tar-fs@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" @@ -16795,15 +16831,15 @@ wait-port@1.0.4: commander "^9.3.0" debug "^4.3.4" -wc-compiler@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.11.0.tgz#fc42d81f23eee0cc98d497aec574cf49c553ae03" - integrity sha512-2reo21bD1jbjqDKbN4rBZ07Om4riP58Llb8Wtc7H7s704Jm8t8yFMKVpNZh3S9/3GpkDZbwp6XKI2MmkYugafQ== +wc-compiler@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.12.0.tgz#f9d04b5bfca505f836487ce63040fdf30f1cea5b" + integrity sha512-eMgHEGhJGcyjSL9p6YIix0ReMViffhtabVEnNE1oRiNlNJL/Fd8e0GZ2gdWhIOV5ZvNMOocyAKFODKpg3CDMkg== dependencies: + "@projectevergreen/acorn-jsx-esm" "~0.1.0" + "@projectevergreen/escodegen-esm" "~0.1.0" acorn "^8.7.0" - acorn-jsx "^5.3.2" acorn-walk "^8.2.0" - escodegen "^2.0.0" parse5 "^6.0.1" wcwidth@^1.0.0, wcwidth@^1.0.1: From a27c8612de376a39a07ed3f767fb18e35379840c Mon Sep 17 00:00:00 2001 From: Paul Barry <devlab2425@gmail.com> Date: Sun, 10 Mar 2024 11:22:26 -0400 Subject: [PATCH 05/47] enhancement/Issue-1118: Single File Bundles for SSR and API routes (#1186) * Issue-1118: Refactor rollup config generation for APIs * Issue-1118: Refactor rollup config generation for SSR * Issue-1118: Refactor forEach to use for-in for the ssr config generation * Issue-1118: Convert forEach to for..in * Issue-1118: Remove unused code * refactor away bundling work arounds and add comments * refactor SSR page bundling to avoid hacky entry point placeholder hack * patch custom element registry check from wcc * refactor SSR page output name from .entry to .route * document breaking changes for adapter plugins * refactor import meta relative asset path escaping * refactor API routes and adapters for mapped API bundles * misc refactoring and docs update * latest WCC patches * windows compatibility * update adapter docs example * remove patches --------- Co-authored-by: Owen Buckley <owenbuckley13@gmail.com> --- .gitignore | 2 +- packages/cli/src/config/rollup.config.js | 163 +++++++++--------- packages/cli/src/lifecycles/bundle.js | 30 ++-- packages/cli/src/lifecycles/serve.js | 5 +- .../build.plugins.adapter/generic-adapter.js | 2 +- .../serve.default.ssr.spec.js | 15 +- packages/plugin-adapter-netlify/src/index.js | 76 +++----- .../cases/build.default/build.default.spec.js | 32 ++++ packages/plugin-adapter-vercel/src/index.js | 65 ++----- .../cases/build.default/build.default.spec.js | 20 +++ www/pages/plugins/adapter.md | 63 +++++-- 11 files changed, 255 insertions(+), 218 deletions(-) diff --git a/.gitignore b/.gitignore index a56ab6ff6..eaa6b83a2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ packages/**/test/**/netlify packages/**/test/**/.netlify packages/**/test/**/.vercel public/ -adapter-outlet/ \ No newline at end of file +adapter-output/ \ No newline at end of file diff --git a/packages/cli/src/config/rollup.config.js b/packages/cli/src/config/rollup.config.js index 7244a9284..b0ea175c9 100644 --- a/packages/cli/src/config/rollup.config.js +++ b/packages/cli/src/config/rollup.config.js @@ -1,3 +1,4 @@ +/* eslint-disable complexity */ import fs from 'fs'; import path from 'path'; import { checkResourceExists, normalizePathnameForWindows } from '../lib/resource-utils.js'; @@ -121,7 +122,8 @@ function greenwoodSyncPageResourceBundlesPlugin(compilation) { } function getMetaImportPath(node) { - return node.arguments[0].value.split('/').join(path.sep); + return node.arguments[0].value.split('/').join(path.sep) + .replace(/\\/g, '/'); // handle Windows style paths } function isNewUrlImportMetaUrl(node) { @@ -157,6 +159,8 @@ function greenwoodImportMetaUrl(compilation) { }).map((plugin) => { return plugin.provider(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(); @@ -235,56 +239,71 @@ function greenwoodImportMetaUrl(compilation) { ? { type, id: normalizePathnameForWindows(url), name } : { type, name: assetName, source: assetContents }; const ref = this.emitFile(emitConfig); - // handle Windows style paths - const normalizedRelativeAssetPath = relativeAssetPath.replace(/\\/g, '/'); const importRef = `import.meta.ROLLUP_FILE_URL_${ref}`; + // loop through all URL bundle chunks from APIs and SSR pages + // and map to their parent file, to pick back up in generateBundle when full hashes are known + if (`${compilation.context.apisDir.pathname}${idAssetName}`.indexOf(normalizedId) >= 0) { + for (const entry of compilation.manifest.apis.keys()) { + const apiRoute = compilation.manifest.apis.get(entry); + + if (normalizedId.endsWith(apiRoute.path)) { + const assets = apiRoute.assets || []; + + assets.push(assetUrl.url.href); + + compilation.manifest.apis.set(entry, { + ...apiRoute, + assets + }); + } + } + } else { + // TODO figure out how to handle URL chunk from SSR pages + // https://github.com/ProjectEvergreen/greenwood/issues/1163 + } + modifiedCode = code - .replace(`'${normalizedRelativeAssetPath}'`, importRef) - .replace(`"${normalizedRelativeAssetPath}"`, importRef); + .replace(`'${relativeAssetPath}'`, importRef) + .replace(`"${relativeAssetPath}"`, importRef); } return { code: modifiedCode ? modifiedCode : code, map: null }; - } - }; -} + }, -// TODO could we use this instead? -// https://github.com/rollup/rollup/blob/v2.79.1/docs/05-plugin-development.md#resolveimportmeta -// https://github.com/ProjectEvergreen/greenwood/issues/1087 -function greenwoodPatchSsrPagesEntryPointRuntimeImport(compilation) { - return { - name: 'greenwood-patch-ssr-pages-entry-point-runtime-import', - generateBundle(options, bundle) { - const { pagesDir, scratchDir } = compilation.context; - - Object.keys(bundle).forEach((key) => { - // map rollup bundle names back to original SSR pages for output bundles and paths - if (key.startsWith('_')) { - const needle = bundle[key].code.match(/___GWD_ENTRY_FILE_URL=(.*.)___/); - - // handle windows shenanigans for facadeModuleId and path separators - if (new URL(`file://${bundle[key].facadeModuleId}`).pathname.startsWith(scratchDir.pathname) && needle) { - const entryPathMatch = needle[1]; - - Object.keys(bundle).forEach((_) => { - // handle windows shenanigans for facadeModuleId and path separators - if (new URL(`file://${bundle[_].facadeModuleId}`).pathname === `${pagesDir.pathname}${entryPathMatch}`) { - bundle[key].code = bundle[key].code.replace(/'___GWD_ENTRY_FILE_URL=(.*.)___'/, `new URL('./${bundle[_].fileName}', import.meta.url)`); - - compilation.graph.forEach((page, idx) => { - if (page.relativeWorkspacePagePath === `/${entryPathMatch}`) { - compilation.graph[idx].outputPath = key; - } - }); - } - }); + generateBundle(options, bundles) { + for (const bundle in bundles) { + const bundleExtension = bundle.split('.').pop(); + const apiKey = `/api/${bundle.replace(`.${bundleExtension}`, '')}`; + + if (compilation.manifest.apis.has(apiKey)) { + const apiManifestDetails = compilation.manifest.apis.get(apiKey); + + for (const reference of bundles[bundle].referencedFiles) { + if (bundles[reference]) { + const assets = apiManifestDetails.assets; + let assetIdx; + + assets.forEach((asset, idx) => { + // more windows shenanigans...) + if (asset.indexOf(bundles[reference]?.facadeModuleId?.replace(/\\/g, '/'))) { + assetIdx = idx; + } + }); + + assets[assetIdx] = new URL(`./api/${reference}`, compilation.context.outputDir).href; + + compilation.manifest.apis.set(apiKey, { + ...apiManifestDetails, + assets + }); + } } } - }); + } } }; } @@ -349,49 +368,40 @@ const getRollupConfigForScriptResources = async (compilation) => { const getRollupConfigForApis = async (compilation) => { const { outputDir, userWorkspace } = compilation.context; - const input = [...compilation.manifest.apis.values()] - .map(api => normalizePathnameForWindows(new URL(`.${api.path}`, userWorkspace))); - // why is this needed? - await fs.promises.mkdir(new URL('./api/assets/', outputDir), { - recursive: true - }); - - // TODO should routes and APIs have chunks? - // https://github.com/ProjectEvergreen/greenwood/issues/1118 - return [{ - input, - output: { - dir: `${normalizePathnameForWindows(outputDir)}/api`, - entryFileNames: '[name].js', - chunkFileNames: '[name].[hash].js' - }, - plugins: [ - greenwoodResourceLoader(compilation), - // support node export conditions for API routes - // https://github.com/ProjectEvergreen/greenwood/issues/1118 - // https://github.com/rollup/plugins/issues/362#issuecomment-873448461 - nodeResolve({ - exportConditions: ['node'], - preferBuiltins: true - }), - commonjs(), - greenwoodImportMetaUrl(compilation) - ] - }]; + return [...compilation.manifest.apis.values()] + .map(api => normalizePathnameForWindows(new URL(`.${api.path}`, userWorkspace))) + .map(filepath => ({ + input: filepath, + output: { + dir: `${normalizePathnameForWindows(outputDir)}/api`, + entryFileNames: '[name].js', + chunkFileNames: '[name].[hash].js' + }, + plugins: [ + greenwoodResourceLoader(compilation), + // support node export conditions for SSR pages + // https://github.com/ProjectEvergreen/greenwood/issues/1118 + // https://github.com/rollup/plugins/issues/362#issuecomment-873448461 + nodeResolve({ + exportConditions: ['node'], + preferBuiltins: true + }), + commonjs(), + greenwoodImportMetaUrl(compilation) + ] + })); }; const getRollupConfigForSsr = async (compilation, input) => { const { outputDir } = compilation.context; - // TODO should routes and APIs have chunks? - // https://github.com/ProjectEvergreen/greenwood/issues/1118 - return [{ - input, + return input.map(filepath => ({ + input: filepath, output: { dir: normalizePathnameForWindows(outputDir), - entryFileNames: '_[name].js', - chunkFileNames: '[name].[hash].js' + entryFileNames: '[name].route.js', + chunkFileNames: '[name].route.chunk.[hash].js' }, plugins: [ greenwoodResourceLoader(compilation), @@ -403,8 +413,7 @@ const getRollupConfigForSsr = async (compilation, input) => { preferBuiltins: true }), commonjs(), - greenwoodImportMetaUrl(compilation), - greenwoodPatchSsrPagesEntryPointRuntimeImport(compilation) // TODO a little hacky but works for now + greenwoodImportMetaUrl(compilation) ], onwarn: (errorObj) => { const { code, message } = errorObj; @@ -424,7 +433,7 @@ const getRollupConfigForSsr = async (compilation, input) => { } } - }]; + })); }; export { diff --git a/packages/cli/src/lifecycles/bundle.js b/packages/cli/src/lifecycles/bundle.js index 625dd4a6d..7cdb62925 100644 --- a/packages/cli/src/lifecycles/bundle.js +++ b/packages/cli/src/lifecycles/bundle.js @@ -185,11 +185,15 @@ async function bundleStyleResources(compilation, resourcePlugins) { async function bundleApiRoutes(compilation) { // https://rollupjs.org/guide/en/#differences-to-the-javascript-api - const [rollupConfig] = await getRollupConfigForApis(compilation); + const apiConfigs = await getRollupConfigForApis(compilation); - if (rollupConfig.input.length !== 0) { - const bundle = await rollup(rollupConfig); - await bundle.write(rollupConfig.output); + if (apiConfigs.length > 0 && apiConfigs[0].input.length !== 0) { + for (const configIndex in apiConfigs) { + const rollupConfig = apiConfigs[configIndex]; + const bundle = await rollup(rollupConfig); + await bundle.write(rollupConfig.output); + + } } } @@ -220,6 +224,8 @@ async function bundleSsrPages(compilation) { // TODO getTemplate has to be static (for now?) // https://github.com/ProjectEvergreen/greenwood/issues/955 const data = await executeRouteModule({ moduleUrl, compilation, page, prerender: false, htmlContents: null, scripts: [], request }); + const pagesPathDiff = compilation.context.pagesDir.pathname.replace(compilation.context.projectDirectory.pathname, ''); + let staticHtml = ''; staticHtml = data.template ? data.template : await getPageTemplate(staticHtml, compilation.context, template, []); @@ -236,13 +242,15 @@ async function bundleSsrPages(compilation) { } // better way to write out this inline code? + // using a URL here produces a bundled chunk, but at leasts its bundled await fs.writeFile(entryFileUrl, ` import { executeRouteModule } from '${normalizePathnameForWindows(executeModuleUrl)}'; + const moduleUrl = new URL('../${pagesPathDiff}${relativeWorkspacePagePath}', import.meta.url); + export async function handler(request) { const compilation = JSON.parse('${JSON.stringify(compilation)}'); const page = JSON.parse('${JSON.stringify(page)}'); - const moduleUrl = '___GWD_ENTRY_FILE_URL=${relativeWorkspacePagePath.replace('/', '')}___'; const data = await executeRouteModule({ moduleUrl, compilation, page, request }); let staticHtml = \`${staticHtml}\`; @@ -258,16 +266,18 @@ async function bundleSsrPages(compilation) { } `); - input.push(normalizePathnameForWindows(moduleUrl)); input.push(normalizePathnameForWindows(entryFileUrl)); } } - const [rollupConfig] = await getRollupConfigForSsr(compilation, input); + const ssrConfigs = await getRollupConfigForSsr(compilation, input); - if (rollupConfig.input.length > 0) { - const bundle = await rollup(rollupConfig); - await bundle.write(rollupConfig.output); + if (ssrConfigs.length > 0 && ssrConfigs[0].input !== '') { + for (const configIndex in ssrConfigs) { + const rollupConfig = ssrConfigs[configIndex]; + const bundle = await rollup(rollupConfig); + await bundle.write(rollupConfig.output); + } } } } diff --git a/packages/cli/src/lifecycles/serve.js b/packages/cli/src/lifecycles/serve.js index 1aaa2e8c4..5ca88ffa1 100644 --- a/packages/cli/src/lifecycles/serve.js +++ b/packages/cli/src/lifecycles/serve.js @@ -335,6 +335,7 @@ async function getHybridServer(compilation) { ctx.status = 200; } else if (isApiRoute) { const apiRoute = manifest.apis.get(url.pathname); + const entryPointUrl = new URL(`.${apiRoute.path}`, outputDir); let body, status, headers, statusText; if (apiRoute.isolation || isolationMode) { @@ -361,12 +362,12 @@ async function getHybridServer(compilation) { }); worker.postMessage({ - href: new URL(`.${apiRoute.path}`, outputDir).href, + href: entryPointUrl.href, request: req }); }); } else { - const { handler } = await import(new URL(`.${apiRoute.path}`, outputDir)); + const { handler } = await import(entryPointUrl); const response = await handler(request); body = response.body; diff --git a/packages/cli/test/cases/build.plugins.adapter/generic-adapter.js b/packages/cli/test/cases/build.plugins.adapter/generic-adapter.js index dbf07037f..8fc6141ba 100644 --- a/packages/cli/test/cases/build.plugins.adapter/generic-adapter.js +++ b/packages/cli/test/cases/build.plugins.adapter/generic-adapter.js @@ -3,7 +3,7 @@ import { checkResourceExists } from '../../../../cli/src/lib/resource-utils.js'; function generateOutputFormat(id, type) { const path = type === 'page' - ? `__${id}` + ? `${id}.route` : `api/${id}`; return ` diff --git a/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js b/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js index 8093b069a..437d948ef 100644 --- a/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js +++ b/packages/cli/test/cases/serve.default.ssr/serve.default.ssr.spec.js @@ -93,9 +93,8 @@ describe('Serve Greenwood With: ', function() { expect(headings[0].textContent).to.equal('Hello from the server rendered home page!'); }); - it('should have the expected bundled SSR output for the page', async function() { - const scriptFiles = (await glob.promise(path.join(this.context.publicDir, '*.js'))) - .filter(file => file.indexOf('index.js') >= 0); + it('should have the expected bundled SSR output for the page entry point and chunk file', async function() { + const scriptFiles = await glob.promise(path.join(this.context.publicDir, 'index*.js')); expect(scriptFiles.length).to.equal(2); }); @@ -229,9 +228,8 @@ describe('Serve Greenwood With: ', function() { expect(resources.find(resource => resource.endsWith('/header.js'))).to.not.be.undefined; }); - it('should have the expected bundled SSR output for the page', async function() { - const scriptFiles = (await glob.promise(path.join(this.context.publicDir, '*.js'))) - .filter(file => file.indexOf('artists.js') >= 0); + it('should have the expected bundled SSR output for the page entry point and chunk file', async function() { + const scriptFiles = await glob.promise(path.join(this.context.publicDir, 'artists*.js')); expect(scriptFiles.length).to.equal(2); }); @@ -267,9 +265,8 @@ describe('Serve Greenwood With: ', function() { expect(cards.length).to.be.greaterThan(0); }); - it('should have the expected bundled SSR output for the page', async function() { - const scriptFiles = (await glob.promise(path.join(this.context.publicDir, '*.js'))) - .filter(file => file.indexOf('users.js') >= 0); + it('should have the expected bundled SSR output for the page entry point and chunk file', async function() { + const scriptFiles = await glob.promise(path.join(this.context.publicDir, 'users*.js')); expect(scriptFiles.length).to.equal(2); }); diff --git a/packages/plugin-adapter-netlify/src/index.js b/packages/plugin-adapter-netlify/src/index.js index e028bcf1f..15ffbf0aa 100644 --- a/packages/plugin-adapter-netlify/src/index.js +++ b/packages/plugin-adapter-netlify/src/index.js @@ -8,7 +8,7 @@ function generateOutputFormat(id) { const handlerAlias = '$handler'; return ` - import { handler as ${handlerAlias} } from './__${id}.js'; + import { handler as ${handlerAlias} } from './${id}.js'; export async function handler (event, context = {}) { const { rawUrl, body, headers = {}, httpMethod } = event; @@ -52,10 +52,9 @@ function generateOutputFormat(id) { } async function setupOutputDirectory(id, outputRoot, outputType) { - const outputFormat = generateOutputFormat(id, outputType); - const filename = outputType === 'api' - ? `api-${id}` - : `${id}`; + const entryPoint = outputType === 'api' ? id : `${id}.route`; + const filename = outputType === 'api' ? `api-${id}` : id; + const outputFormat = generateOutputFormat(entryPoint, outputType); await fs.mkdir(outputRoot, { recursive: true }); await fs.writeFile(new URL(`./${filename}.js`, outputRoot), outputFormat); @@ -64,7 +63,7 @@ async function setupOutputDirectory(id, outputRoot, outputType) { })); } -// TODO manifest options, like node version? +// TODO do we need more manifest options, like node version? // https://github.com/netlify/zip-it-and-ship-it#options async function createOutputZip(id, outputType, outputRootUrl, projectDirectory) { const filename = outputType === 'api' @@ -92,51 +91,29 @@ async function netlifyAdapter(compilation) { await fs.mkdir(adapterOutputUrl, { recursive: true }); } - const files = await fs.readdir(outputDir); - const isExecuteRouteModule = files.find(file => file.startsWith('execute-route-module')); - await fs.mkdir(new URL('./netlify/functions/', projectDirectory), { recursive: true }); for (const page of ssrPages) { const { id } = page; const outputType = 'page'; const outputRoot = new URL(`./${id}/`, adapterOutputUrl); + const files = (await fs.readdir(outputDir)) + .filter(file => file.startsWith(`${id}.route.chunk.`) && file.endsWith('.js')); await setupOutputDirectory(id, outputRoot, outputType); + // handle user's actual route entry file await fs.cp( - new URL(`./_${id}.js`, outputDir), - new URL(`./_${id}.js`, outputRoot), - { recursive: true } - ); - await fs.cp( - new URL(`./__${id}.js`, outputDir), - new URL(`./__${id}.js`, outputRoot), + new URL(`./${id}.route.js`, outputDir), + new URL(`./${id}.route.js`, outputRoot), { recursive: true } ); - // TODO quick hack to make serverless pages are fully self-contained - // for example, execute-route-module.js will only get code split if there are more than one SSR pages - // https://github.com/ProjectEvergreen/greenwood/issues/1118 - if (isExecuteRouteModule) { + // and the URL chunk for renderer plugin and executeRouteModule + for (const file of files) { await fs.cp( - new URL(`./${isExecuteRouteModule}`, outputDir), - new URL(`./${isExecuteRouteModule}`, outputRoot) - ); - } - - // TODO how to track SSR resources that get dumped out in the public directory? - // https://github.com/ProjectEvergreen/greenwood/issues/1118 - const ssrPageAssets = (await fs.readdir(outputDir)) - .filter(file => !path.basename(file).startsWith('_') - && !path.basename(file).startsWith('execute') - && path.basename(file).endsWith('.js') - ); - - for (const asset of ssrPageAssets) { - await fs.cp( - new URL(`./${asset}`, outputDir), - new URL(`./${asset}`, outputRoot), + new URL(`./${file}`, outputDir), + new URL(`./${file}`, outputRoot), { recursive: true } ); } @@ -151,36 +128,25 @@ async function netlifyAdapter(compilation) { redirects += `${basePath}/api/* /.netlify/functions/api-:splat 200`; } - for (const [key] of apiRoutes) { + for (const [key, value] of apiRoutes.entries()) { const outputType = 'api'; const id = key.replace(`${basePath}/api/`, ''); const outputRoot = new URL(`./api/${id}/`, adapterOutputUrl); - + const { assets = [] } = value; await setupOutputDirectory(id, outputRoot, outputType); - // TODO ideally all functions would be self contained - // https://github.com/ProjectEvergreen/greenwood/issues/1118 await fs.cp( new URL(`./api/${id}.js`, outputDir), - new URL(`./__${id}.js`, outputRoot), + new URL(`./${id}.js`, outputRoot), { recursive: true } ); - if (await checkResourceExists(new URL('./api/assets/', outputDir))) { - await fs.cp( - new URL('./api/assets/', outputDir), - new URL('./assets/', outputRoot), - { recursive: true } - ); - } - - const ssrApiAssets = (await fs.readdir(new URL('./api/', outputDir))) - .filter(file => new RegExp(/^[\w][\w-]*\.[a-zA-Z0-9]{4,20}\.[\w]{2,4}$/).test(path.basename(file))); + for (const asset of assets) { + const name = path.basename(asset); - for (const asset of ssrApiAssets) { await fs.cp( - new URL(`./${asset}`, new URL('./api/', outputDir)), - new URL(`./${asset}`, outputRoot), + new URL(asset), + new URL(`./${name}`, outputRoot), { recursive: true } ); } diff --git a/packages/plugin-adapter-netlify/test/cases/build.default/build.default.spec.js b/packages/plugin-adapter-netlify/test/cases/build.default/build.default.spec.js index e51a2059d..612b27d6d 100644 --- a/packages/plugin-adapter-netlify/test/cases/build.default/build.default.spec.js +++ b/packages/plugin-adapter-netlify/test/cases/build.default/build.default.spec.js @@ -125,6 +125,22 @@ describe('Build Greenwood With: ', function() { expect(headers.get('content-type')).to.be.equal('application/json'); expect(JSON.parse(body).message).to.be.equal(`Hello ${param}!`); }); + + it('should not have a shared asset for the card component', async () => { + const name = path.basename(apiFunctions[0]).replace('.zip', ''); + + await extract(apiFunctions[0], { + dir: path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), name) + }); + + const assets = await glob.promise(path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), `/${name}/*`)); + const exists = assets.find((asset) => { + const name = asset.split('/').pop(); + return name.startsWith('card') && name.endsWith('.js'); + }); + + expect(!!exists).to.equal(false); + }); }); describe('Fragments API Route adapter', function() { @@ -158,6 +174,22 @@ describe('Build Greenwood With: ', function() { expect(cardTags.length).to.be.equal(2); expect(headers.get('content-type')).to.be.equal('text/html'); }); + + it('should have a shared asset for the card component', async () => { + const name = path.basename(apiFunctions[0]).replace('.zip', ''); + + await extract(apiFunctions[0], { + dir: path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), name) + }); + + const assets = await glob.promise(path.join(normalizePathnameForWindows(netlifyFunctionsOutputUrl), `/${name}/*`)); + const exists = assets.find((asset) => { + const name = asset.split('/').pop(); + return name.startsWith('card') && name.endsWith('.js'); + }); + + expect(!!exists).to.equal(true); + }); }); describe('Submit JSON API Route adapter', function() { diff --git a/packages/plugin-adapter-vercel/src/index.js b/packages/plugin-adapter-vercel/src/index.js index 77602ae88..f37e8154c 100644 --- a/packages/plugin-adapter-vercel/src/index.js +++ b/packages/plugin-adapter-vercel/src/index.js @@ -6,7 +6,7 @@ import { checkResourceExists } from '@greenwood/cli/src/lib/resource-utils.js'; function generateOutputFormat(id, type) { const handlerAlias = '$handler'; const path = type === 'page' - ? `__${id}` + ? `${id}.route` : id; return ` @@ -82,85 +82,52 @@ async function vercelAdapter(compilation) { 'version': 3 })); - const files = await fs.readdir(outputDir); - const isExecuteRouteModule = files.find(file => file.startsWith('execute-route-module')); - for (const page of ssrPages) { const outputType = 'page'; const { id } = page; const outputRoot = new URL(`./${basePath}/${id}.func/`, adapterOutputUrl); + const files = (await fs.readdir(outputDir)) + .filter(file => file.startsWith(`${id}.route.chunk.`) && file.endsWith('.js')); await setupFunctionBuildFolder(id, outputType, outputRoot); + // handle user's actual route entry file await fs.cp( - new URL(`./_${id}.js`, outputDir), - new URL(`./_${id}.js`, outputRoot), - { recursive: true } - ); - - await fs.cp( - new URL(`./__${id}.js`, outputDir), - new URL(`./__${id}.js`, outputRoot), + new URL(`./${id}.route.js`, outputDir), + new URL(`./${id}.route.js`, outputRoot), { recursive: true } ); - // TODO quick hack to make serverless pages are fully self-contained - // for example, execute-route-module.js will only get code split if there are more than one SSR pages - // https://github.com/ProjectEvergreen/greenwood/issues/1118 - if (isExecuteRouteModule) { - await fs.cp( - new URL(`./${isExecuteRouteModule}`, outputDir), - new URL(`./${isExecuteRouteModule}`, outputRoot) - ); - } - - // TODO how to track SSR resources that get dumped out in the public directory? - // https://github.com/ProjectEvergreen/greenwood/issues/1118 - const ssrPageAssets = (await fs.readdir(outputDir)) - .filter(file => !path.basename(file).startsWith('_') - && !path.basename(file).startsWith('execute') - && path.basename(file).endsWith('.js') - ); - - for (const asset of ssrPageAssets) { + // and the URL chunk for renderer plugin and executeRouteModule + for (const file of files) { await fs.cp( - new URL(`./${asset}`, outputDir), - new URL(`./${asset}`, outputRoot), + new URL(`./${file}`, outputDir), + new URL(`./${file}`, outputRoot), { recursive: true } ); } } - for (const [key] of apiRoutes) { + for (const [key, value] of apiRoutes.entries()) { const outputType = 'api'; const id = key.replace(`${basePath}/api/`, ''); const outputRoot = new URL(`./${basePath}/api/${id}.func/`, adapterOutputUrl); + const { assets = [] } = value; await setupFunctionBuildFolder(id, outputType, outputRoot); - // TODO ideally all functions would be self contained - // https://github.com/ProjectEvergreen/greenwood/issues/1118 await fs.cp( new URL(`./api/${id}.js`, outputDir), new URL(`./${id}.js`, outputRoot), { recursive: true } ); - if (await checkResourceExists(new URL('./api/assets/', outputDir))) { - await fs.cp( - new URL('./api/assets/', outputDir), - new URL('./assets/', outputRoot), - { recursive: true } - ); - } - - const ssrApiAssets = (await fs.readdir(new URL('./api/', outputDir))) - .filter(file => new RegExp(/^[\w][\w-]*\.[a-zA-Z0-9]{4,20}\.[\w]{2,4}$/).test(path.basename(file))); + for (const asset of assets) { + const name = path.basename(asset); - for (const asset of ssrApiAssets) { await fs.cp( - new URL(`./${asset}`, new URL('./api/', outputDir)), - new URL(`./${asset}`, outputRoot), + new URL(asset), + new URL(`./${name}`, outputRoot), { recursive: true } ); } diff --git a/packages/plugin-adapter-vercel/test/cases/build.default/build.default.spec.js b/packages/plugin-adapter-vercel/test/cases/build.default/build.default.spec.js index 2c6f64239..e4ddf27a2 100644 --- a/packages/plugin-adapter-vercel/test/cases/build.default/build.default.spec.js +++ b/packages/plugin-adapter-vercel/test/cases/build.default/build.default.spec.js @@ -149,6 +149,16 @@ describe('Build Greenwood With: ', function() { expect(headers.get('content-type')).to.be.equal('application/json'); expect(JSON.parse(body).message).to.be.equal(`Hello ${param}!`); }); + + it('should not have a shared asset for the card component', async () => { + const assets = await glob.promise(path.join(normalizePathnameForWindows(vercelFunctionsOutputUrl), '/api/greeting.func/*')); + const exists = assets.find((asset) => { + const name = asset.split('/').pop(); + return name.startsWith('card') && name.endsWith('.js'); + }); + + expect(!!exists).to.equal(false); + }); }); describe('Fragments API Route adapter', function() { @@ -183,6 +193,16 @@ describe('Build Greenwood With: ', function() { expect(cardTags.length).to.be.equal(2); expect(headers.get('content-type')).to.be.equal('text/html'); }); + + it('should have a shared asset for the card component', async () => { + const assets = await glob.promise(path.join(normalizePathnameForWindows(vercelFunctionsOutputUrl), '/api/fragment.func/*')); + const exists = assets.find((asset) => { + const name = asset.split('/').pop(); + return name.startsWith('card') && name.endsWith('.js'); + }); + + expect(!!exists).to.equal(true); + }); }); describe('Submit JSON API Route adapter', function() { diff --git a/www/pages/plugins/adapter.md b/www/pages/plugins/adapter.md index 76bab683b..21180b430 100644 --- a/www/pages/plugins/adapter.md +++ b/www/pages/plugins/adapter.md @@ -46,7 +46,7 @@ import { checkResourceExists } from '../../../../cli/src/lib/resource-utils.js'; function generateOutputFormat(id, type) { const path = type === 'page' - ? `__${id}` + ? `${id}.entry` : `api/${id}`; return ` @@ -72,23 +72,58 @@ async function genericAdapter(compilation) { await fs.mkdir(adapterOutputUrl); } + // SSR pages for (const page of ssrPages) { const { id } = page; const outputFormat = generateOutputFormat(id, 'page'); + const files = (await fs.readdir(outputDir)) + .filter(file => file.startsWith(`${id}.route.chunk.`) && file.endsWith('.js')); + + // generate a facade for all SSR pages for your particular hosting provider + await fs.writeFile(new URL('./index.js', adapterOutputUrl), outputFormat); + + // handle user's actual route entry file, appended with .route by Greenwood + await fs.cp( + new URL(`./${id}.route.js`, outputDir), + new URL(`./${id}.route.js`, outputRoot), + { recursive: true } + ); + + // and the URL generated chunk for the route + for (const file of files) { + await fs.cp( + new URL(`./${file}`, outputDir), + new URL(`./${file}`, outputRoot), + { recursive: true } + ); + } - // generate a shim for all SSR pages - await fs.writeFile(new URL(`./${id}.js`, adapterOutputUrl), outputFormat); - - // copy all entry points - await fs.cp(new URL(`./_${id}.js`, outputDir), new URL(`./_${id}.js`, adapterOutputUrl)); - await fs.cp(new URL(`./__${id}.js`, outputDir), new URL(`./_${id}.js`, adapterOutputUrl)); - - // generate a manifest - await fs.writeFile(new URL('./metadata.json', adapterOutputUrl), JSON.stringify({ - version: '1.0.0', - runtime: 'nodejs' - // ... - })); + // API routes + for (const [key, value] of apiRoutes.entries()) { + const outputType = 'api'; + const id = key.replace(`${basePath}/api/`, ''); + const outputRoot = new URL(`./${basePath}/api/${id}.func/`, adapterOutputUrl); + const { assets = [] } = value; + + await setupFunctionBuildFolder(id, outputType, outputRoot); + + await fs.cp( + new URL(`./api/${id}.js`, outputDir), + new URL(`./${id}.js`, outputRoot), + { recursive: true } + ); + + // copy any child assets, like URL bundles + for (const asset of assets) { + const name = path.basename(asset); + + await fs.cp( + new URL(asset), + new URL(`./${name}`, outputRoot), + { recursive: true } + ); + } + } } } From 1370ec9afcf57662dcbbdb86b9fcf89e6dc927e5 Mon Sep 17 00:00:00 2001 From: Owen Buckley <owenbuckley13@gmail.com> Date: Sun, 10 Mar 2024 14:17:08 -0400 Subject: [PATCH 06/47] v0.30.0-alpha.0 --- lerna.json | 2 +- packages/cli/package.json | 2 +- packages/init/package.json | 2 +- packages/plugin-adapter-netlify/package.json | 4 ++-- packages/plugin-adapter-vercel/package.json | 4 ++-- packages/plugin-babel/package.json | 4 ++-- packages/plugin-google-analytics/package.json | 4 ++-- packages/plugin-graphql/package.json | 4 ++-- packages/plugin-import-commonjs/package.json | 4 ++-- packages/plugin-import-css/package.json | 4 ++-- packages/plugin-import-json/package.json | 4 ++-- packages/plugin-import-jsx/package.json | 4 ++-- packages/plugin-include-html/package.json | 4 ++-- packages/plugin-polyfills/package.json | 4 ++-- packages/plugin-postcss/package.json | 4 ++-- packages/plugin-renderer-lit/package.json | 4 ++-- packages/plugin-renderer-puppeteer/package.json | 4 ++-- packages/plugin-typescript/package.json | 4 ++-- www/package.json | 4 ++++ 19 files changed, 37 insertions(+), 33 deletions(-) diff --git a/lerna.json b/lerna.json index b547049dd..3f27d0838 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.29.4", + "version": "0.30.0-alpha.0", "packages": [ "packages/*", "www" diff --git a/packages/cli/package.json b/packages/cli/package.json index 635220224..baaee3653 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/cli", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "Greenwood CLI.", "type": "module", "repository": "https://github.com/ProjectEvergreen/greenwood", diff --git a/packages/init/package.json b/packages/init/package.json index 4eeb727fd..ca5236c42 100644 --- a/packages/init/package.json +++ b/packages/init/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/init", - "version": "0.29.3", + "version": "0.30.0-alpha.0", "description": "A package for scaffolding a new Greenwood project.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/init", diff --git a/packages/plugin-adapter-netlify/package.json b/packages/plugin-adapter-netlify/package.json index aba1fa793..be40602ea 100644 --- a/packages/plugin-adapter-netlify/package.json +++ b/packages/plugin-adapter-netlify/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-adapter-netlify", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin for supporting Netlify serverless and edge runtimes.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-adapter-netlify", @@ -31,7 +31,7 @@ "zip-a-folder": "^2.0.0" }, "devDependencies": { - "@greenwood/cli": "^0.29.4", + "@greenwood/cli": "^0.30.0-alpha.0", "extract-zip": "^2.0.1" } } diff --git a/packages/plugin-adapter-vercel/package.json b/packages/plugin-adapter-vercel/package.json index 13b95432d..8a2d40b64 100644 --- a/packages/plugin-adapter-vercel/package.json +++ b/packages/plugin-adapter-vercel/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-adapter-vercel", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin for supporting Vercel serverless and edge runtimes.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-adapter-vercel", @@ -27,6 +27,6 @@ "@greenwood/cli": "^0.28.0" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-babel/package.json b/packages/plugin-babel/package.json index a6304848c..a62468e8f 100644 --- a/packages/plugin-babel/package.json +++ b/packages/plugin-babel/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-babel", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin for using Babel and applying it to your JavaScript.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-babel", @@ -36,6 +36,6 @@ "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-private-methods": "^7.10.4", "@babel/runtime": "^7.10.4", - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-google-analytics/package.json b/packages/plugin-google-analytics/package.json index dba466792..608bbf4d9 100644 --- a/packages/plugin-google-analytics/package.json +++ b/packages/plugin-google-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-google-analytics", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin adding support for Google Analytics JavaScript tracker.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-google-analytics", @@ -26,6 +26,6 @@ "@greenwood/cli": "^0.4.0" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-graphql/package.json b/packages/plugin-graphql/package.json index 46f151f02..d7d24ae57 100644 --- a/packages/plugin-graphql/package.json +++ b/packages/plugin-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-graphql", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A plugin for using GraphQL for querying your content.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-graphql", @@ -33,6 +33,6 @@ "graphql-tag": "^2.10.1" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-import-commonjs/package.json b/packages/plugin-import-commonjs/package.json index ba86d7268..e6b70a72c 100644 --- a/packages/plugin-import-commonjs/package.json +++ b/packages/plugin-import-commonjs/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-import-commonjs", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A plugin for loading CommonJS based modules in the browser using ESM (import / export) syntax.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-commonjs", @@ -31,7 +31,7 @@ "cjs-module-lexer": "^1.0.0" }, "devDependencies": { - "@greenwood/cli": "^0.29.4", + "@greenwood/cli": "^0.30.0-alpha.0", "lodash": "^4.17.20" } } diff --git a/packages/plugin-import-css/package.json b/packages/plugin-import-css/package.json index e8ff33811..6ac03e183 100644 --- a/packages/plugin-import-css/package.json +++ b/packages/plugin-import-css/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-import-css", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin to allow you to use ESM (import) syntax to load your CSS.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-css", @@ -27,6 +27,6 @@ "@greenwood/cli": "^0.4.0" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-import-json/package.json b/packages/plugin-import-json/package.json index 96701138f..67c1215cb 100644 --- a/packages/plugin-import-json/package.json +++ b/packages/plugin-import-json/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-import-json", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin to allow you to use ESM (import) syntax to load your JSON.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-json", @@ -27,6 +27,6 @@ "@greenwood/cli": "^0.12.3" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-import-jsx/package.json b/packages/plugin-import-jsx/package.json index 7ef5f9900..29752d891 100644 --- a/packages/plugin-import-jsx/package.json +++ b/packages/plugin-import-jsx/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-import-jsx", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin to write JSX rendering Web Components compatible with WCC.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-jsx", @@ -30,6 +30,6 @@ "wc-compiler": "~0.12.0" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-include-html/package.json b/packages/plugin-include-html/package.json index 1dc63fd1b..685b80981 100644 --- a/packages/plugin-include-html/package.json +++ b/packages/plugin-include-html/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-include-html", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin to let you render server side JS from HTML or JS at build time as HTML.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-include-html", @@ -27,6 +27,6 @@ "@greenwood/cli": "^0.4.0" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-polyfills/package.json b/packages/plugin-polyfills/package.json index 26c0766e8..cd6db43b5 100644 --- a/packages/plugin-polyfills/package.json +++ b/packages/plugin-polyfills/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-polyfills", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin adding support for Web Component related polyfills like Custom Elements and Shadow DOM.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-polyfills", @@ -29,6 +29,6 @@ "@webcomponents/webcomponentsjs": "^2.6.0" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-postcss/package.json b/packages/plugin-postcss/package.json index 9ad29be7a..8331ef3cc 100644 --- a/packages/plugin-postcss/package.json +++ b/packages/plugin-postcss/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-postcss", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin for loading PostCSS configuration and applying it to your CSS.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-postcss", @@ -31,6 +31,6 @@ "postcss-preset-env": "^7.0.1" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-renderer-lit/package.json b/packages/plugin-renderer-lit/package.json index 1738aa2c2..a1e5b4e44 100644 --- a/packages/plugin-renderer-lit/package.json +++ b/packages/plugin-renderer-lit/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-renderer-lit", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A server-side rendering plugin for Lit based Greenwood projects.", "type": "module", "repository": "https://github.com/ProjectEvergreen/greenwood", @@ -32,7 +32,7 @@ "@lit-labs/ssr-client": "^1.1.6" }, "devDependencies": { - "@greenwood/cli": "^0.29.4", + "@greenwood/cli": "^0.30.0-alpha.0", "lit": "^3.1.0" } } diff --git a/packages/plugin-renderer-puppeteer/package.json b/packages/plugin-renderer-puppeteer/package.json index ec6944729..012fd9e6b 100644 --- a/packages/plugin-renderer-puppeteer/package.json +++ b/packages/plugin-renderer-puppeteer/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-renderer-puppeteer", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin to allow headless browser rendering with Puppeteer.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-renderer-puppeteer", @@ -30,6 +30,6 @@ "puppeteer": "^15.3.2" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/packages/plugin-typescript/package.json b/packages/plugin-typescript/package.json index 45096c4ec..b575c120f 100644 --- a/packages/plugin-typescript/package.json +++ b/packages/plugin-typescript/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-typescript", - "version": "0.29.4", + "version": "0.30.0-alpha.0", "description": "A Greenwood plugin for writing TypeScript.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-typescript", @@ -29,6 +29,6 @@ "typescript": "^5.1.6" }, "devDependencies": { - "@greenwood/cli": "^0.29.4" + "@greenwood/cli": "^0.30.0-alpha.0" } } diff --git a/www/package.json b/www/package.json index 1731541bf..9bbc1c2de 100644 --- a/www/package.json +++ b/www/package.json @@ -1,6 +1,10 @@ { "name": "@greenwood/www", +<<<<<<< HEAD "version": "0.29.3", +======= + "version": "0.30.0-alpha.0", +>>>>>>> 205f5dfa (v0.30.0-alpha.0) "private": true, "type": "module", "description": "Greenwood website workspace.", From a0ce9d0235e7b2f49e06a5e0f0c9d19dd8ce2010 Mon Sep 17 00:00:00 2001 From: Owen Buckley <owenbuckley13@gmail.com> Date: Sat, 16 Mar 2024 19:13:58 -0400 Subject: [PATCH 07/47] ensure minimum compatible version of wcc 0.12.1 (#1209) --- packages/cli/package.json | 2 +- packages/plugin-import-jsx/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index baaee3653..5c3aacb03 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -52,7 +52,7 @@ "remark-rehype": "^7.0.0", "rollup": "^3.29.4", "unified": "^9.2.0", - "wc-compiler": "~0.12.0" + "wc-compiler": "~0.12.1" }, "devDependencies": { "@babel/runtime": "^7.10.4", diff --git a/packages/plugin-import-jsx/package.json b/packages/plugin-import-jsx/package.json index 29752d891..8a9036d10 100644 --- a/packages/plugin-import-jsx/package.json +++ b/packages/plugin-import-jsx/package.json @@ -27,7 +27,7 @@ "@greenwood/cli": "^0.28.0-alpha.4" }, "dependencies": { - "wc-compiler": "~0.12.0" + "wc-compiler": "~0.12.1" }, "devDependencies": { "@greenwood/cli": "^0.30.0-alpha.0" diff --git a/yarn.lock b/yarn.lock index e09df1dad..7e8057dcb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16831,10 +16831,10 @@ wait-port@1.0.4: commander "^9.3.0" debug "^4.3.4" -wc-compiler@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.12.0.tgz#f9d04b5bfca505f836487ce63040fdf30f1cea5b" - integrity sha512-eMgHEGhJGcyjSL9p6YIix0ReMViffhtabVEnNE1oRiNlNJL/Fd8e0GZ2gdWhIOV5ZvNMOocyAKFODKpg3CDMkg== +wc-compiler@~0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.12.1.tgz#cf34a1a9758c0085580ddc82b5aa6312d3de20bf" + integrity sha512-+uEK9cL9MHIWnXoD11cQ77omBLekw1Oih2wqbrM2jBFGTPHvPauq3jtdWzlkg/0OwOJOyYqbBlQfZb/oA+HuNg== dependencies: "@projectevergreen/acorn-jsx-esm" "~0.1.0" "@projectevergreen/escodegen-esm" "~0.1.0" From a13d8090f9e8955d855f6b4bfa1cce1007f92bfa Mon Sep 17 00:00:00 2001 From: Owen Buckley <owenbuckley13@gmail.com> Date: Sat, 16 Mar 2024 19:30:22 -0400 Subject: [PATCH 08/47] bug/issue 1118 SSR pages are missing URL chunks of route chunk (#1210) * naive work around for missing chunks of route chunk * fix linting * namespace SSR page route entry points and route chunks --- packages/cli/src/config/rollup.config.js | 4 ++-- packages/plugin-adapter-netlify/src/index.js | 12 ++++++------ packages/plugin-adapter-vercel/src/index.js | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/config/rollup.config.js b/packages/cli/src/config/rollup.config.js index b0ea175c9..e9103edc9 100644 --- a/packages/cli/src/config/rollup.config.js +++ b/packages/cli/src/config/rollup.config.js @@ -400,8 +400,8 @@ const getRollupConfigForSsr = async (compilation, input) => { input: filepath, output: { dir: normalizePathnameForWindows(outputDir), - entryFileNames: '[name].route.js', - chunkFileNames: '[name].route.chunk.[hash].js' + entryFileNames: `${path.basename(filepath).split('.')[0]}.route.js`, + chunkFileNames: `${path.basename(filepath).split('.')[0]}.route.chunk.[hash].js` }, plugins: [ greenwoodResourceLoader(compilation), diff --git a/packages/plugin-adapter-netlify/src/index.js b/packages/plugin-adapter-netlify/src/index.js index 15ffbf0aa..d874f2d65 100644 --- a/packages/plugin-adapter-netlify/src/index.js +++ b/packages/plugin-adapter-netlify/src/index.js @@ -97,8 +97,8 @@ async function netlifyAdapter(compilation) { const { id } = page; const outputType = 'page'; const outputRoot = new URL(`./${id}/`, adapterOutputUrl); - const files = (await fs.readdir(outputDir)) - .filter(file => file.startsWith(`${id}.route.chunk.`) && file.endsWith('.js')); + const chunks = (await fs.readdir(outputDir)) + .filter(file => file.startsWith(`${id}.route.chunk`) && file.endsWith('.js')); await setupOutputDirectory(id, outputRoot, outputType); @@ -109,11 +109,11 @@ async function netlifyAdapter(compilation) { { recursive: true } ); - // and the URL chunk for renderer plugin and executeRouteModule - for (const file of files) { + // and any (URL) chunks for the page + for (const chunk of chunks) { await fs.cp( - new URL(`./${file}`, outputDir), - new URL(`./${file}`, outputRoot), + new URL(`./${chunk}`, outputDir), + new URL(`./${chunk}`, outputRoot), { recursive: true } ); } diff --git a/packages/plugin-adapter-vercel/src/index.js b/packages/plugin-adapter-vercel/src/index.js index f37e8154c..0353e2fa5 100644 --- a/packages/plugin-adapter-vercel/src/index.js +++ b/packages/plugin-adapter-vercel/src/index.js @@ -86,8 +86,8 @@ async function vercelAdapter(compilation) { const outputType = 'page'; const { id } = page; const outputRoot = new URL(`./${basePath}/${id}.func/`, adapterOutputUrl); - const files = (await fs.readdir(outputDir)) - .filter(file => file.startsWith(`${id}.route.chunk.`) && file.endsWith('.js')); + const chunks = (await fs.readdir(outputDir)) + .filter(file => file.startsWith(`${id}.route.chunk`) && file.endsWith('.js')); await setupFunctionBuildFolder(id, outputType, outputRoot); @@ -98,11 +98,11 @@ async function vercelAdapter(compilation) { { recursive: true } ); - // and the URL chunk for renderer plugin and executeRouteModule - for (const file of files) { + // and any (URL) chunks for the page + for (const chunk of chunks) { await fs.cp( - new URL(`./${file}`, outputDir), - new URL(`./${file}`, outputRoot), + new URL(`./${chunk}`, outputDir), + new URL(`./${chunk}`, outputRoot), { recursive: true } ); } From 3925dbc3e5f8a0e6e93727b0212129ee0b2ff197 Mon Sep 17 00:00:00 2001 From: Owen Buckley <owenbuckley13@gmail.com> Date: Sat, 16 Mar 2024 19:31:28 -0400 Subject: [PATCH 09/47] v0.30.0-alpha.1 --- lerna.json | 2 +- packages/cli/package.json | 2 +- packages/init/package.json | 2 +- packages/plugin-adapter-netlify/package.json | 4 ++-- packages/plugin-adapter-vercel/package.json | 4 ++-- packages/plugin-babel/package.json | 4 ++-- packages/plugin-google-analytics/package.json | 4 ++-- packages/plugin-graphql/package.json | 4 ++-- packages/plugin-import-commonjs/package.json | 4 ++-- packages/plugin-import-css/package.json | 4 ++-- packages/plugin-import-json/package.json | 4 ++-- packages/plugin-import-jsx/package.json | 4 ++-- packages/plugin-include-html/package.json | 4 ++-- packages/plugin-polyfills/package.json | 4 ++-- packages/plugin-postcss/package.json | 4 ++-- packages/plugin-renderer-lit/package.json | 4 ++-- packages/plugin-renderer-puppeteer/package.json | 4 ++-- packages/plugin-typescript/package.json | 4 ++-- www/package.json | 6 +----- 19 files changed, 34 insertions(+), 38 deletions(-) diff --git a/lerna.json b/lerna.json index 3f27d0838..db36a6099 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "packages": [ "packages/*", "www" diff --git a/packages/cli/package.json b/packages/cli/package.json index 5c3aacb03..23af05e1d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/cli", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "Greenwood CLI.", "type": "module", "repository": "https://github.com/ProjectEvergreen/greenwood", diff --git a/packages/init/package.json b/packages/init/package.json index ca5236c42..d4149f64a 100644 --- a/packages/init/package.json +++ b/packages/init/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/init", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A package for scaffolding a new Greenwood project.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/init", diff --git a/packages/plugin-adapter-netlify/package.json b/packages/plugin-adapter-netlify/package.json index be40602ea..b26450fac 100644 --- a/packages/plugin-adapter-netlify/package.json +++ b/packages/plugin-adapter-netlify/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-adapter-netlify", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin for supporting Netlify serverless and edge runtimes.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-adapter-netlify", @@ -31,7 +31,7 @@ "zip-a-folder": "^2.0.0" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0", + "@greenwood/cli": "^0.30.0-alpha.1", "extract-zip": "^2.0.1" } } diff --git a/packages/plugin-adapter-vercel/package.json b/packages/plugin-adapter-vercel/package.json index 8a2d40b64..3070fdded 100644 --- a/packages/plugin-adapter-vercel/package.json +++ b/packages/plugin-adapter-vercel/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-adapter-vercel", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin for supporting Vercel serverless and edge runtimes.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-adapter-vercel", @@ -27,6 +27,6 @@ "@greenwood/cli": "^0.28.0" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-babel/package.json b/packages/plugin-babel/package.json index a62468e8f..90dc95577 100644 --- a/packages/plugin-babel/package.json +++ b/packages/plugin-babel/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-babel", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin for using Babel and applying it to your JavaScript.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-babel", @@ -36,6 +36,6 @@ "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-private-methods": "^7.10.4", "@babel/runtime": "^7.10.4", - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-google-analytics/package.json b/packages/plugin-google-analytics/package.json index 608bbf4d9..4c5a7ccb1 100644 --- a/packages/plugin-google-analytics/package.json +++ b/packages/plugin-google-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-google-analytics", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin adding support for Google Analytics JavaScript tracker.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-google-analytics", @@ -26,6 +26,6 @@ "@greenwood/cli": "^0.4.0" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-graphql/package.json b/packages/plugin-graphql/package.json index d7d24ae57..89b29735f 100644 --- a/packages/plugin-graphql/package.json +++ b/packages/plugin-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-graphql", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A plugin for using GraphQL for querying your content.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-graphql", @@ -33,6 +33,6 @@ "graphql-tag": "^2.10.1" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-import-commonjs/package.json b/packages/plugin-import-commonjs/package.json index e6b70a72c..45103bdd0 100644 --- a/packages/plugin-import-commonjs/package.json +++ b/packages/plugin-import-commonjs/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-import-commonjs", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A plugin for loading CommonJS based modules in the browser using ESM (import / export) syntax.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-commonjs", @@ -31,7 +31,7 @@ "cjs-module-lexer": "^1.0.0" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0", + "@greenwood/cli": "^0.30.0-alpha.1", "lodash": "^4.17.20" } } diff --git a/packages/plugin-import-css/package.json b/packages/plugin-import-css/package.json index 6ac03e183..15733c402 100644 --- a/packages/plugin-import-css/package.json +++ b/packages/plugin-import-css/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-import-css", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin to allow you to use ESM (import) syntax to load your CSS.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-css", @@ -27,6 +27,6 @@ "@greenwood/cli": "^0.4.0" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-import-json/package.json b/packages/plugin-import-json/package.json index 67c1215cb..dfc168f34 100644 --- a/packages/plugin-import-json/package.json +++ b/packages/plugin-import-json/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-import-json", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin to allow you to use ESM (import) syntax to load your JSON.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-json", @@ -27,6 +27,6 @@ "@greenwood/cli": "^0.12.3" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-import-jsx/package.json b/packages/plugin-import-jsx/package.json index 8a9036d10..b51e9d4cb 100644 --- a/packages/plugin-import-jsx/package.json +++ b/packages/plugin-import-jsx/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-import-jsx", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin to write JSX rendering Web Components compatible with WCC.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-jsx", @@ -30,6 +30,6 @@ "wc-compiler": "~0.12.1" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-include-html/package.json b/packages/plugin-include-html/package.json index 685b80981..90ff8d62b 100644 --- a/packages/plugin-include-html/package.json +++ b/packages/plugin-include-html/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-include-html", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin to let you render server side JS from HTML or JS at build time as HTML.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-include-html", @@ -27,6 +27,6 @@ "@greenwood/cli": "^0.4.0" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-polyfills/package.json b/packages/plugin-polyfills/package.json index cd6db43b5..b203a5d1f 100644 --- a/packages/plugin-polyfills/package.json +++ b/packages/plugin-polyfills/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-polyfills", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin adding support for Web Component related polyfills like Custom Elements and Shadow DOM.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-polyfills", @@ -29,6 +29,6 @@ "@webcomponents/webcomponentsjs": "^2.6.0" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-postcss/package.json b/packages/plugin-postcss/package.json index 8331ef3cc..f527c1919 100644 --- a/packages/plugin-postcss/package.json +++ b/packages/plugin-postcss/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-postcss", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin for loading PostCSS configuration and applying it to your CSS.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-postcss", @@ -31,6 +31,6 @@ "postcss-preset-env": "^7.0.1" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-renderer-lit/package.json b/packages/plugin-renderer-lit/package.json index a1e5b4e44..770239ac8 100644 --- a/packages/plugin-renderer-lit/package.json +++ b/packages/plugin-renderer-lit/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-renderer-lit", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A server-side rendering plugin for Lit based Greenwood projects.", "type": "module", "repository": "https://github.com/ProjectEvergreen/greenwood", @@ -32,7 +32,7 @@ "@lit-labs/ssr-client": "^1.1.6" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0", + "@greenwood/cli": "^0.30.0-alpha.1", "lit": "^3.1.0" } } diff --git a/packages/plugin-renderer-puppeteer/package.json b/packages/plugin-renderer-puppeteer/package.json index 012fd9e6b..fa64a32b9 100644 --- a/packages/plugin-renderer-puppeteer/package.json +++ b/packages/plugin-renderer-puppeteer/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-renderer-puppeteer", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin to allow headless browser rendering with Puppeteer.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-renderer-puppeteer", @@ -30,6 +30,6 @@ "puppeteer": "^15.3.2" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/packages/plugin-typescript/package.json b/packages/plugin-typescript/package.json index b575c120f..a70f8214c 100644 --- a/packages/plugin-typescript/package.json +++ b/packages/plugin-typescript/package.json @@ -1,6 +1,6 @@ { "name": "@greenwood/plugin-typescript", - "version": "0.30.0-alpha.0", + "version": "0.30.0-alpha.1", "description": "A Greenwood plugin for writing TypeScript.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-typescript", @@ -29,6 +29,6 @@ "typescript": "^5.1.6" }, "devDependencies": { - "@greenwood/cli": "^0.30.0-alpha.0" + "@greenwood/cli": "^0.30.0-alpha.1" } } diff --git a/www/package.json b/www/package.json index 9bbc1c2de..2c58384d5 100644 --- a/www/package.json +++ b/www/package.json @@ -1,10 +1,6 @@ { "name": "@greenwood/www", -<<<<<<< HEAD - "version": "0.29.3", -======= - "version": "0.30.0-alpha.0", ->>>>>>> 205f5dfa (v0.30.0-alpha.0) + "version": "0.30.0-alpha.1", "private": true, "type": "module", "description": "Greenwood website workspace.", From 981791576d73bb809a99c4e16860b5738237b11b Mon Sep 17 00:00:00 2001 From: Owen Buckley <owenbuckley13@gmail.com> Date: Wed, 20 Mar 2024 21:01:24 -0400 Subject: [PATCH 10/47] remove Lit renderer plugin duplicate caveats section (#1214) --- packages/plugin-renderer-lit/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/plugin-renderer-lit/README.md b/packages/plugin-renderer-lit/README.md index e5928e9d1..c5f867b51 100644 --- a/packages/plugin-renderer-lit/README.md +++ b/packages/plugin-renderer-lit/README.md @@ -86,13 +86,6 @@ export async function getBody() { } ``` -## Caveats - -There are a few considerations to take into account when using a `LitElement` as your page component: -- Lit SSR [**only** renders into declarative shadow roots](https://github.com/lit/lit/issues/3080#issuecomment-1165158794), so you will have to keep browser support and polyfill usage in mind. -- Depending on your use case, SSR bundling may break due to bundle chunking and code splitting by Rollup, which we are [hoping to correct ASAP](https://github.com/ProjectEvergreen/greenwood/issues/1118). -- At this time, `LitElement` does [not support `async` work](https://lit.dev/docs/ssr/overview/#library-status) which makes data fetching in pages a bit of challenge. You can follow along with this issue [in the Lit repo](https://github.com/lit/lit/issues/2469). - ## Options ### Isolation Mode From cadf3b7eacf30a562d674b5126893bc54046b5ab Mon Sep 17 00:00:00 2001 From: Owen Buckley <owenbuckley13@gmail.com> Date: Mon, 29 Apr 2024 15:33:39 -0400 Subject: [PATCH 11/47] feature/issue 923 native import attributes for CSS and JSON (#1215) * 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 --- .eslintrc.cjs | 23 +- .../workflows/{ci-exp.yml => ci-loaders.yml} | 4 +- .../{ci-win-exp.yml => ci-win-loaders.yml} | 4 +- .gitignore | 1 + .ls-lint.yml | 1 + .nvmrc | 2 +- greenwood.config.js | 11 +- package.json | 16 +- packages/cli/package.json | 6 +- packages/cli/src/commands/build.js | 4 + packages/cli/src/config/rollup.config.js | 75 ++- packages/cli/src/lib/resource-utils.js | 11 +- packages/cli/src/lifecycles/bundle.js | 25 +- packages/cli/src/lifecycles/prerender.js | 4 + packages/cli/src/lifecycles/serve.js | 36 ++ packages/cli/src/loader.js | 49 +- .../plugins/resource/plugin-node-modules.js | 7 +- .../plugins/resource/plugin-standard-css.js | 23 +- .../plugins/resource/plugin-standard-json.js | 18 + .../loaders-build.import-attributes.spec.js | 77 +++ .../src/components/card/card.css | 3 + .../src/components/card/card.js | 27 ++ .../src/components/card/card.json | 5 + .../src/pages/index.html | 11 + .../greenwood.config.js | 3 + ...-build.prerender-import-attributes.spec.js | 97 ++++ .../package.json | 3 + .../src/components/hero/hero.css | 18 + .../src/components/hero/hero.js | 40 ++ .../src/components/hero/hero.json | 3 + .../src/index.html | 12 + ...ders-develop.ssr-import-attributes.spec.js | 186 +++++++ .../src/api/fragment.js | 17 + .../src/components/card/card.css | 3 + .../src/components/card/card.js | 27 ++ .../src/components/card/card.json | 5 + .../src/pages/greeting.js | 10 + ...erve.default.ssr-import-attributes.spec.js | 167 +++++++ .../src/api/fragment.js | 17 + .../src/components/card/card.css | 3 + .../src/components/card/card.js | 27 ++ .../src/components/card/card.json | 5 + .../src/pages/greeting.js | 10 + packages/plugin-babel/src/index.js | 4 +- packages/plugin-graphql/README.md | 4 +- .../greenwood.config.js | 0 .../loaders-prerender.query-children.spec.js} | 0 .../package.json | 0 .../src/components/posts-list.js | 2 +- .../src/pages/blog/first-post/index.md | 0 .../src/pages/blog/second-post/index.md | 0 .../src/pages/index.html | 0 packages/plugin-import-css/README.md | 2 + packages/plugin-import-css/package.json | 4 +- .../test/cases/default/default.spec.js | 2 +- .../develop.default/develop.default.spec.js | 2 +- .../exp-build.prerender.spec.js | 2 +- .../cases/exp-serve.ssr/exp-serve.ssr.spec.js | 2 +- packages/plugin-import-json/README.md | 2 + packages/plugin-import-json/package.json | 4 +- .../test/cases/default/default.spec.js | 2 +- .../develop.default/develop.default.spec.js | 2 +- .../exp-build.prerender.spec.js | 2 +- .../cases/exp-serve.ssr/exp-serve.ssr.spec.js | 2 +- packages/plugin-import-jsx/README.md | 11 +- packages/plugin-import-jsx/package.json | 2 +- packages/plugin-import-jsx/src/index.js | 7 +- .../test/cases/default/src/templates/app.html | 2 +- .../greenwood.config.js | 0 .../loaders-build.prerender.spec.js} | 3 +- .../package.json | 0 .../src/components/footer.jsx | 0 .../cases/loaders-build.prerender/src/main.js | 1 + .../src/pages/index.md | 0 .../src/templates/app.html | 2 +- packages/plugin-import-raw/README.md | 63 +++ packages/plugin-import-raw/package.json | 31 ++ packages/plugin-import-raw/src/index.js | 67 +++ .../build.matchers/build.matchers.spec.js | 84 ++++ .../cases/build.matchers/greenwood.config.js | 12 + .../test/cases/build.matchers/src/main.js | 3 + .../cases/build.matchers/src/pages/index.html | 12 + .../test/cases/default/default.spec.js | 83 ++++ .../test/cases/default/greenwood.config.js | 8 + .../test/cases/default/src/main.js | 3 + .../test/cases/default/src/pages/index.html | 12 + .../test/cases/default/src/styles.css | 3 + .../develop.default/develop.default.spec.js | 126 +++++ .../cases/develop.default/greenwood.config.js | 7 + .../test/cases/develop.default/package.json | 4 + .../test/cases/develop.default/src/main.css | 12 + .../cases/develop.default/src/styles.css.js | 1 + .../greenwood.config.js | 8 + .../loaders-build.prerender.spec.js | 90 ++++ .../loaders-build.prerender/package.json | 4 + .../src/components/footer.css | 1 + .../src/components/footer.js | 25 + .../src/pages/index.md | 3 + .../src/templates/app.html | 12 + .../loaders-serve.ssr/greenwood.config.js | 7 + .../loaders-serve.ssr.spec.js | 152 ++++++ .../test/cases/loaders-serve.ssr/package.json | 4 + .../loaders-serve.ssr/src/api/fragment.js | 28 ++ .../loaders-serve.ssr/src/components/card.css | 44 ++ .../loaders-serve.ssr/src/components/card.js | 31 ++ .../loaders-serve.ssr/src/pages/products.js | 31 ++ .../src/services/products.js | 11 + .../loaders-serve.ssr/src/styles/some.css | 3 + packages/plugin-postcss/README.md | 2 - packages/plugin-postcss/src/index.js | 8 +- packages/plugin-renderer-lit/README.md | 5 +- .../build.prerender.getting-started.spec.js | 6 +- .../greenwood.config.js | 9 + ...-build.prerender.import-attributes.spec.js | 193 ++++++++ .../package.json | 7 + .../src/components/header/header.css | 5 + .../src/components/header/header.js | 27 ++ .../src/components/header/nav.json | 3 + .../src/pages/index.html | 19 + .../cases/serve.default/serve.default.spec.js | 10 +- .../greenwood.config.js | 0 .../loaders-serve.prerender-ssr.spec.js} | 0 .../package.json | 0 .../src/components/card/card.ts | 0 .../src/pages/index.html | 0 .../greenwood.config.js | 0 .../loaders-serve.ssr.spec.js} | 0 .../package.json | 0 .../src/api/fragment.js | 0 .../src/components/card/card.ts | 0 .../src/components/card/logo.png | Bin .../src/components/card/styles.ts | 0 .../src/pages/index.html | 0 tsconfig.json | 5 +- www/components/banner/banner.js | 4 +- www/components/banner/eve-button.js | 2 +- www/components/card/card.js | 2 +- www/components/header/header.js | 2 +- www/components/shelf/shelf.js | 2 +- www/components/social-icons/social-icons.js | 2 +- www/pages/docs/scripts.md | 23 +- www/pages/docs/server-rendering.md | 25 +- www/pages/plugins/custom-plugins.md | 3 +- www/pages/plugins/resource.md | 94 ++-- yarn.lock | 457 +++++++++++++----- 145 files changed, 2770 insertions(+), 292 deletions(-) rename .github/workflows/{ci-exp.yml => ci-loaders.yml} (89%) rename .github/workflows/{ci-win-exp.yml => ci-win-loaders.yml} (85%) create mode 100644 packages/cli/test/cases/loaders-build.import-attributes/loaders-build.import-attributes.spec.js create mode 100644 packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.css create mode 100644 packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.js create mode 100644 packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.json create mode 100644 packages/cli/test/cases/loaders-build.import-attributes/src/pages/index.html create mode 100644 packages/cli/test/cases/loaders-build.prerender-import-attributes/greenwood.config.js create mode 100644 packages/cli/test/cases/loaders-build.prerender-import-attributes/loaders-build.prerender-import-attributes.spec.js create mode 100644 packages/cli/test/cases/loaders-build.prerender-import-attributes/package.json create mode 100644 packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.css create mode 100644 packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.js create mode 100644 packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.json create mode 100644 packages/cli/test/cases/loaders-build.prerender-import-attributes/src/index.html create mode 100644 packages/cli/test/cases/loaders-develop.ssr-import-attributes/loaders-develop.ssr-import-attributes.spec.js create mode 100644 packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/api/fragment.js create mode 100644 packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.css create mode 100644 packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.js create mode 100644 packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.json create mode 100644 packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/pages/greeting.js create mode 100644 packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/loaders-serve.default.ssr-import-attributes.spec.js create mode 100644 packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/api/fragment.js create mode 100644 packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.css create mode 100644 packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.js create mode 100644 packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.json create mode 100644 packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/pages/greeting.js rename packages/plugin-graphql/test/cases/{exp-prerender.query-children => loaders-prerender.query-children}/greenwood.config.js (100%) rename packages/plugin-graphql/test/cases/{exp-prerender.query-children/exp-prerender.query-children.spec.js => loaders-prerender.query-children/loaders-prerender.query-children.spec.js} (100%) rename packages/plugin-graphql/test/cases/{exp-prerender.query-children => loaders-prerender.query-children}/package.json (100%) rename packages/plugin-graphql/test/cases/{exp-prerender.query-children => loaders-prerender.query-children}/src/components/posts-list.js (95%) rename packages/plugin-graphql/test/cases/{exp-prerender.query-children => loaders-prerender.query-children}/src/pages/blog/first-post/index.md (100%) rename packages/plugin-graphql/test/cases/{exp-prerender.query-children => loaders-prerender.query-children}/src/pages/blog/second-post/index.md (100%) rename packages/plugin-graphql/test/cases/{exp-prerender.query-children => loaders-prerender.query-children}/src/pages/index.html (100%) rename packages/plugin-import-jsx/test/cases/{exp-build.prerender => loaders-build.prerender}/greenwood.config.js (100%) rename packages/plugin-import-jsx/test/cases/{exp-build.prerender/exp-build.prerender.spec.js => loaders-build.prerender/loaders-build.prerender.spec.js} (97%) rename packages/plugin-import-jsx/test/cases/{exp-build.prerender => loaders-build.prerender}/package.json (100%) rename packages/plugin-import-jsx/test/cases/{exp-build.prerender => loaders-build.prerender}/src/components/footer.jsx (100%) create mode 100644 packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/main.js rename packages/plugin-import-jsx/test/cases/{exp-build.prerender => loaders-build.prerender}/src/pages/index.md (100%) rename packages/plugin-import-jsx/test/cases/{exp-build.prerender => loaders-build.prerender}/src/templates/app.html (61%) create mode 100644 packages/plugin-import-raw/README.md create mode 100644 packages/plugin-import-raw/package.json create mode 100644 packages/plugin-import-raw/src/index.js create mode 100644 packages/plugin-import-raw/test/cases/build.matchers/build.matchers.spec.js create mode 100644 packages/plugin-import-raw/test/cases/build.matchers/greenwood.config.js create mode 100644 packages/plugin-import-raw/test/cases/build.matchers/src/main.js create mode 100644 packages/plugin-import-raw/test/cases/build.matchers/src/pages/index.html create mode 100644 packages/plugin-import-raw/test/cases/default/default.spec.js create mode 100644 packages/plugin-import-raw/test/cases/default/greenwood.config.js create mode 100644 packages/plugin-import-raw/test/cases/default/src/main.js create mode 100644 packages/plugin-import-raw/test/cases/default/src/pages/index.html create mode 100644 packages/plugin-import-raw/test/cases/default/src/styles.css create mode 100644 packages/plugin-import-raw/test/cases/develop.default/develop.default.spec.js create mode 100644 packages/plugin-import-raw/test/cases/develop.default/greenwood.config.js create mode 100644 packages/plugin-import-raw/test/cases/develop.default/package.json create mode 100644 packages/plugin-import-raw/test/cases/develop.default/src/main.css create mode 100644 packages/plugin-import-raw/test/cases/develop.default/src/styles.css.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-build.prerender/greenwood.config.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-build.prerender/package.json create mode 100644 packages/plugin-import-raw/test/cases/loaders-build.prerender/src/components/footer.css create mode 100644 packages/plugin-import-raw/test/cases/loaders-build.prerender/src/components/footer.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-build.prerender/src/pages/index.md create mode 100644 packages/plugin-import-raw/test/cases/loaders-build.prerender/src/templates/app.html create mode 100644 packages/plugin-import-raw/test/cases/loaders-serve.ssr/greenwood.config.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-serve.ssr/loaders-serve.ssr.spec.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-serve.ssr/package.json create mode 100644 packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/api/fragment.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/components/card.css create mode 100644 packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/components/card.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/pages/products.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/services/products.js create mode 100644 packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/styles/some.css create mode 100644 packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/greenwood.config.js create mode 100644 packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/loaders-build.prerender.import-attributes.spec.js create mode 100644 packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/package.json create mode 100644 packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/src/components/header/header.css create mode 100644 packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/src/components/header/header.js create mode 100644 packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/src/components/header/nav.json create mode 100644 packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/src/pages/index.html rename packages/plugin-typescript/test/cases/{exp-prerender.serve.ssr => loaders-serve.prerender-ssr}/greenwood.config.js (100%) rename packages/plugin-typescript/test/cases/{exp-prerender.serve.ssr/exp-prerender.serve.ssr.spec.js => loaders-serve.prerender-ssr/loaders-serve.prerender-ssr.spec.js} (100%) rename packages/plugin-typescript/test/cases/{exp-prerender.serve.ssr => loaders-serve.prerender-ssr}/package.json (100%) rename packages/plugin-typescript/test/cases/{exp-prerender.serve.ssr => loaders-serve.prerender-ssr}/src/components/card/card.ts (100%) rename packages/plugin-typescript/test/cases/{exp-prerender.serve.ssr => loaders-serve.prerender-ssr}/src/pages/index.html (100%) rename packages/plugin-typescript/test/cases/{exp-serve.ssr => loaders-serve.ssr}/greenwood.config.js (100%) rename packages/plugin-typescript/test/cases/{exp-serve.ssr/exp-serve.ssr.spec.js => loaders-serve.ssr/loaders-serve.ssr.spec.js} (100%) rename packages/plugin-typescript/test/cases/{exp-serve.ssr => loaders-serve.ssr}/package.json (100%) rename packages/plugin-typescript/test/cases/{exp-serve.ssr => loaders-serve.ssr}/src/api/fragment.js (100%) rename packages/plugin-typescript/test/cases/{exp-serve.ssr => loaders-serve.ssr}/src/components/card/card.ts (100%) rename packages/plugin-typescript/test/cases/{exp-serve.ssr => loaders-serve.ssr}/src/components/card/logo.png (100%) rename packages/plugin-typescript/test/cases/{exp-serve.ssr => loaders-serve.ssr}/src/components/card/styles.ts (100%) rename packages/plugin-typescript/test/cases/{exp-serve.ssr => loaders-serve.ssr}/src/pages/index.html (100%) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 0c06c5089..dce41cebc 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -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 diff --git a/.github/workflows/ci-exp.yml b/.github/workflows/ci-loaders.yml similarity index 89% rename from .github/workflows/ci-exp.yml rename to .github/workflows/ci-loaders.yml index f4916828d..34c079cbd 100644 --- a/.github/workflows/ci-exp.yml +++ b/.github/workflows/ci-loaders.yml @@ -1,4 +1,4 @@ -name: Continuous Integration (Experimental) +name: Continuous Integration (Loaders) on: [pull_request] @@ -25,4 +25,4 @@ jobs: yarn install --frozen-lockfile && yarn lerna bootstrap - name: Test run: | - yarn test:exp \ No newline at end of file + yarn test:loaders \ No newline at end of file diff --git a/.github/workflows/ci-win-exp.yml b/.github/workflows/ci-win-loaders.yml similarity index 85% rename from .github/workflows/ci-win-exp.yml rename to .github/workflows/ci-win-loaders.yml index ffc7ef0fd..7e69f3873 100644 --- a/.github/workflows/ci-win-exp.yml +++ b/.github/workflows/ci-win-loaders.yml @@ -1,4 +1,4 @@ -name: Continuous Integration Windows (Experimental) +name: Continuous Integration Windows (Loaders) on: [pull_request] @@ -22,4 +22,4 @@ jobs: yarn install --frozen-lockfile --network-timeout 1000000 && yarn lerna bootstrap - name: Test run: | - yarn test:exp:win \ No newline at end of file + yarn test:loaders:win \ No newline at end of file diff --git a/.gitignore b/.gitignore index eaa6b83a2..bd89ae68f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.ls-lint.yml b/.ls-lint.yml index 64ab822b4..ab2ad9cba 100644 --- a/.ls-lint.yml +++ b/.ls-lint.yml @@ -14,5 +14,6 @@ ls: ignore: - .git - node_modules + - packages/plugin-babel/node_modules - packages/init/node_modules - packages/plugin-typescript/node_modules \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 72c7744b3..23cc58a71 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.12.1 \ No newline at end of file +18.20.0 \ No newline at end of file diff --git a/greenwood.config.js b/greenwood.config.js index 648fdc361..216952137 100644 --- a/greenwood.config.js +++ b/greenwood.config.js @@ -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'; @@ -18,8 +17,12 @@ export default { lit: true }), greenwoodPluginPostCss(), - greenwoodPluginImportJson(), - greenwoodPluginImportCss(), + greenwoodPluginImportRaw({ + matches: [ + 'eve-button.css', + 'eve-container.css' + ] + }), greenwoodPluginIncludeHTML(), greenwoodPluginRendererPuppeteer(), { diff --git a/package.json b/package.json index fe99893d8..fe45e2b24 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/cli/package.json b/packages/cli/package.json index 23af05e1d..0143d4d63 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -18,7 +18,7 @@ "NodeJS" ], "engines": { - "node": ">=18.12.1" + "node": ">=18.20.0" }, "bin": { "greenwood": "./src/index.js" @@ -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", @@ -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", diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index fba4ef73f..e2cc575fe 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -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); } diff --git a/packages/cli/src/config/rollup.config.js b/packages/cli/src/config/rollup.config.js index e9103edc9..73ddb24b9 100644 --- a/packages/cli/src/config/rollup.config.js +++ b/packages/cli/src/config/rollup.config.js @@ -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) { @@ -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()); } } @@ -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; } } @@ -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 }); } diff --git a/packages/cli/src/lib/resource-utils.js b/packages/cli/src/lib/resource-utils.js index 1032afc5e..a08cd9c57 100644 --- a/packages/cli/src/lib/resource-utils.js +++ b/packages/cli/src/lib/resource-utils.js @@ -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) { @@ -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 diff --git a/packages/cli/src/lifecycles/bundle.js b/packages/cli/src/lifecycles/bundle.js index 7cdb62925..2ffd9f0a1 100644 --- a/packages/cli/src/lifecycles/bundle.js +++ b/packages/cli/src/lifecycles/bundle.js @@ -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); } @@ -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()); @@ -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; @@ -302,6 +322,7 @@ const bundleCompilation = async (compilation) => { return plugin.provider(compilation); }).filter((provider) => { return provider.shouldIntercept && provider.intercept + || provider.shouldPreIntercept && provider.preIntercept || provider.shouldOptimize && provider.optimize; }); diff --git a/packages/cli/src/lifecycles/prerender.js b/packages/cli/src/lifecycles/prerender.js index 6eb1a3438..804cfd4a1 100644 --- a/packages/cli/src/lifecycles/prerender.js +++ b/packages/cli/src/lifecycles/prerender.js @@ -32,6 +32,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); } diff --git a/packages/cli/src/lifecycles/serve.js b/packages/cli/src/lifecycles/serve.js index 5ca88ffa1..57a87f80f 100644 --- a/packages/cli/src/lifecycles/serve.js +++ b/packages/cli/src/lifecycles/serve.js @@ -88,6 +88,42 @@ async function getDevServer(compilation) { await next(); }); + // allow pre-processing of userland plugins _before_ Greenwood "standardizes" it + app.use(async (ctx, next) => { + try { + const url = new URL(ctx.url); + const { header, status, message } = ctx.response; + const request = transformKoaRequestIntoStandardRequest(url, ctx.request); + const initResponse = new Response(status === 204 ? null : ctx.body, { + statusText: message, + status, + headers: new Headers(header) + }); + const response = await resourcePlugins.reduce(async (responsePromise, plugin) => { + const intermediateResponse = await responsePromise; + if (plugin.shouldPreIntercept && await plugin.shouldPreIntercept(url, request, intermediateResponse.clone())) { + const current = await plugin.preIntercept(url, request, await intermediateResponse.clone()); + const merged = mergeResponse(intermediateResponse.clone(), current); + + return Promise.resolve(merged); + } else { + return Promise.resolve(await responsePromise); + } + }, Promise.resolve(initResponse.clone())); + + ctx.body = response.body ? Readable.from(response.body) : ''; + ctx.message = response.statusText; + response.headers.forEach((value, key) => { + ctx.set(key, value); + }); + } catch (e) { + ctx.status = 500; + console.error(e); + } + + await next(); + }); + // allow intercepting of responses for URLs app.use(async (ctx, next) => { try { diff --git a/packages/cli/src/loader.js b/packages/cli/src/loader.js index 238fefcd0..5d347bc20 100644 --- a/packages/cli/src/loader.js +++ b/packages/cli/src/loader.js @@ -1,19 +1,23 @@ -import fs from 'fs/promises'; import { readAndMergeConfig as initConfig } from './lifecycles/config.js'; const config = await initConfig(); -const resourcePlugins = config.plugins.filter(plugin => plugin.type === 'resource' && !plugin.isGreenwoodDefaultPlugin).map(plugin => plugin.provider({ +const resourcePlugins = config.plugins.filter(plugin => plugin.type === 'resource').map(plugin => plugin.provider({ context: { projectDirectory: new URL(`file://${process.cwd()}`) - } + }, + config: { + devServer: {} + }, + graph: [] })); -async function getCustomLoaderResponse(url, body = '', checkOnly = false) { - const headers = new Headers({ - 'Content-Type': 'text/javascript' - }); - const request = new Request(url.href, { headers }); - const initResponse = new Response(body, { headers }); +async function getCustomLoaderResponse(url, checkOnly = false) { + const headers = { + 'Accept': 'text/javascript', + 'Sec-Fetch-Dest': 'empty' + }; + const request = new Request(url, { headers }); + const initResponse = new Response(''); let response = initResponse.clone(); let shouldHandle = false; @@ -28,6 +32,14 @@ async function getCustomLoaderResponse(url, body = '', checkOnly = false) { } for (const plugin of resourcePlugins) { + if (plugin.shouldPreIntercept && await plugin.shouldPreIntercept(url, request, response.clone())) { + shouldHandle = true; + + if (!checkOnly) { + response = await plugin.preIntercept(url, request, response.clone()); + } + } + if (plugin.shouldIntercept && await plugin.shouldIntercept(url, request, response.clone())) { shouldHandle = true; @@ -53,7 +65,7 @@ export async function resolve(specifier, context, defaultResolve) { : undefined; if (url) { - const { shouldHandle } = await getCustomLoaderResponse(url, null, true); + const { shouldHandle } = await getCustomLoaderResponse(url, true); if (shouldHandle) { return { @@ -69,19 +81,16 @@ export async function resolve(specifier, context, defaultResolve) { // https://nodejs.org/docs/latest-v18.x/api/esm.html#loadurl-context-nextload export async function load(source, context, defaultLoad) { const extension = source.split('.').pop(); - const url = new URL(`${source}?type=${extension}`); - const { shouldHandle } = await getCustomLoaderResponse(url, null, true); + const url = new URL(source); + const { shouldHandle } = await getCustomLoaderResponse(url, true); - if (shouldHandle) { - const contents = await fs.readFile(url, 'utf-8'); - const { response } = await getCustomLoaderResponse(url, contents); - const body = await response.text(); + if (shouldHandle && extension !== 'js') { + const { response } = await getCustomLoaderResponse(url); + const contents = await response.text(); - // TODO better way to handle remove export default? leverage import assertions instead - // https://github.com/ProjectEvergreen/greenwood/issues/923 return { - format: extension === 'json' ? 'json' : 'module', - source: extension === 'json' ? JSON.stringify(JSON.parse(contents.replace('export default ', ''))) : body, + format: 'module', + source: contents, shortCircuit: true }; } diff --git a/packages/cli/src/plugins/resource/plugin-node-modules.js b/packages/cli/src/plugins/resource/plugin-node-modules.js index 475efbf18..ff05c4446 100644 --- a/packages/cli/src/plugins/resource/plugin-node-modules.js +++ b/packages/cli/src/plugins/resource/plugin-node-modules.js @@ -29,7 +29,7 @@ class NodeModulesResource extends ResourceInterface { // https://github.com/ProjectEvergreen/greenwood/issues/953v async resolve(url) { const { projectDirectory } = this.compilation.context; - const { pathname } = url; + const { pathname, searchParams } = url; const packageName = getPackageNameFromUrl(pathname); const absoluteNodeModulesLocation = await getNodeModulesLocationForPackage(packageName); const packagePathPieces = pathname.split('node_modules/')[1].split('/'); // double split to handle node_modules within nested paths @@ -37,8 +37,11 @@ class NodeModulesResource extends ResourceInterface { const absoluteNodeModulesPathname = absoluteNodeModulesLocation ? `${absoluteNodeModulesLocation}${packagePathPieces.join('/').replace(packageName, '')}` : (await resolveForRelativeUrl(url, projectDirectory)).pathname; + const params = searchParams.size > 0 + ? `?${searchParams.toString()}` + : ''; - return new Request(`file://${absoluteNodeModulesPathname}`); + return new Request(`file://${absoluteNodeModulesPathname}${params}`); } async shouldServe(url) { diff --git a/packages/cli/src/plugins/resource/plugin-standard-css.js b/packages/cli/src/plugins/resource/plugin-standard-css.js index d52c723e2..f9cfbe664 100644 --- a/packages/cli/src/plugins/resource/plugin-standard-css.js +++ b/packages/cli/src/plugins/resource/plugin-standard-css.js @@ -227,11 +227,30 @@ class StandardCssResource extends ResourceInterface { }); } + async shouldIntercept(url, request) { + const { pathname, searchParams } = url; + const ext = pathname.split('.').pop(); + + return url.protocol === 'file:' && ext === this.extensions[0] && request.headers.get('Accept')?.indexOf('text/javascript') >= 0 && !searchParams.has('type'); + } + + async intercept(url, request, response) { + const contents = (await response.text()).replace(/\r?\n|\r/g, ' ').replace(/\\/g, '\\\\'); + const body = `const sheet = new CSSStyleSheet();sheet.replaceSync(\`${contents}\`);export default sheet;`; + + return new Response(body, { + headers: { + 'Content-Type': 'text/javascript' + } + }); + } + async shouldOptimize(url, response) { - const { protocol, pathname } = url; + const { protocol, pathname, searchParams } = url; const isValidCss = pathname.split('.').pop() === this.extensions[0] && protocol === 'file:' - && response.headers.get('Content-Type').indexOf(this.contentType) >= 0; + && response.headers.get('Content-Type').indexOf(this.contentType) >= 0 + && searchParams.get('type') !== 'css'; return this.compilation.config.optimization !== 'none' && isValidCss; } diff --git a/packages/cli/src/plugins/resource/plugin-standard-json.js b/packages/cli/src/plugins/resource/plugin-standard-json.js index 726c9b5c1..07d9883aa 100644 --- a/packages/cli/src/plugins/resource/plugin-standard-json.js +++ b/packages/cli/src/plugins/resource/plugin-standard-json.js @@ -40,6 +40,24 @@ class StandardJsonResource extends ResourceInterface { }) }); } + + async shouldIntercept(url, request) { + const { protocol, pathname, searchParams } = url; + const ext = pathname.split('.').pop(); + + return protocol === 'file:' && request.headers.get('Accept')?.indexOf('text/javascript') >= 0 && ext === this.extensions[0] && !searchParams.has('type'); + } + + async intercept(url, request, response) { + const json = await response.json(); + const body = `export default ${JSON.stringify(json)}`; + + return new Response(body, { + headers: { + 'Content-Type': 'text/javascript' + } + }); + } } const pluginGreenwoodStandardJson = [{ diff --git a/packages/cli/test/cases/loaders-build.import-attributes/loaders-build.import-attributes.spec.js b/packages/cli/test/cases/loaders-build.import-attributes/loaders-build.import-attributes.spec.js new file mode 100644 index 000000000..7d474741c --- /dev/null +++ b/packages/cli/test/cases/loaders-build.import-attributes/loaders-build.import-attributes.spec.js @@ -0,0 +1,77 @@ +/* + * Use Case + * Run Greenwood serve command with no config for using import attributes with a basic static bundles. + * + * User Result + * Should start the development server and render a bare bones Greenwood build. + * + * User Command + * greenwood serve + * + * User Config + * {} + * + * User Workspace + * src/ + * components/ + * card/ + * card.css + * card.js + * card.json + * pages/ + * index.html + * + */ +import chai from 'chai'; +import fs from 'fs'; +import glob from 'glob-promise'; +import path from 'path'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Build Greenwood With: ', function() { + const LABEL = 'Import Attributes used in static pages'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + const hostname = 'http://localhost:8080'; + let runner; + + before(function() { + this.context = { + hostname + }; + runner = new Runner(false, true); + }); + + describe(LABEL, function() { + + before(async function() { + runner.setup(outputPath); + runner.runCommand(cliPath, 'build'); + }); + + describe('Importing CSS w/ Constructable Stylesheets', function() { + let scripts; + + before(async function() { + scripts = await glob.promise(path.join(outputPath, 'public/card.*.js')); + }); + + it('should have the expected output from importing hero.css as a Constructable Stylesheet', function() { + const scriptContents = fs.readFileSync(scripts[0], 'utf-8'); + + expect(scriptContents).to.contain('const e=new CSSStyleSheet;e.replaceSync(":host { color: red; }");'); + }); + }); + }); + + after(function() { + runner.stopCommand(); + runner.teardown([ + path.join(outputPath, '.greenwood'), + path.join(outputPath, 'node_modules') + ]); + }); +}); \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.css b/packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.css new file mode 100644 index 000000000..2ace1c18d --- /dev/null +++ b/packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.css @@ -0,0 +1,3 @@ +:host { + color: red; +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.js b/packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.js new file mode 100644 index 000000000..49a154086 --- /dev/null +++ b/packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.js @@ -0,0 +1,27 @@ +import sheet from './card.css' with { type: 'css' }; +import data from './card.json' with { type: 'json' }; + +export default class Card extends HTMLElement { + + connectedCallback() { + if (!this.shadowRoot) { + const name = this.getAttribute('name') || 'World'; + const template = document.createElement('template'); + + template.innerHTML = ` + <div class="card"> + <img alt="logo" href="${data.image.url}"> + <h2>Hello, ${name}!</h2> + </div> + <hr/> + `; + + this.attachShadow({ mode: 'open' }); + this.shadowRoot.appendChild(template.content.cloneNode(true)); + } + + this.shadowRoot.adoptedStyleSheets = [sheet]; + } +} + +customElements.define('app-card', Card); \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.json b/packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.json new file mode 100644 index 000000000..cf759ab71 --- /dev/null +++ b/packages/cli/test/cases/loaders-build.import-attributes/src/components/card/card.json @@ -0,0 +1,5 @@ +{ + "image": { + "url": "/path/to/image.webp" + } +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.import-attributes/src/pages/index.html b/packages/cli/test/cases/loaders-build.import-attributes/src/pages/index.html new file mode 100644 index 000000000..ed61c4594 --- /dev/null +++ b/packages/cli/test/cases/loaders-build.import-attributes/src/pages/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <script type="module" src="../components/card/card.js"></script> + </head> + + <body> + <h1>The home page</h1> + <app-card></app-card> + </body> +</html> \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.prerender-import-attributes/greenwood.config.js b/packages/cli/test/cases/loaders-build.prerender-import-attributes/greenwood.config.js new file mode 100644 index 000000000..8dc4be464 --- /dev/null +++ b/packages/cli/test/cases/loaders-build.prerender-import-attributes/greenwood.config.js @@ -0,0 +1,3 @@ +export default { + prerender: true +}; \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.prerender-import-attributes/loaders-build.prerender-import-attributes.spec.js b/packages/cli/test/cases/loaders-build.prerender-import-attributes/loaders-build.prerender-import-attributes.spec.js new file mode 100644 index 000000000..7b765e769 --- /dev/null +++ b/packages/cli/test/cases/loaders-build.prerender-import-attributes/loaders-build.prerender-import-attributes.spec.js @@ -0,0 +1,97 @@ +/* + * Use Case + * Run Greenwood with prerendering of CSS and JSON being referenced using import attributes. + * + * User Result + * Should generate a static Greenwood build with CSS properly prerendered. + * + * User Command + * greenwood build + * + * User Config + * import { greenwoodPluginImportCss } from '@greenwood/plugin-import-css'; + * + * { + * prerender: true, + * } + * + * User Workspace + * src/ + * components/ + * hero/ + * hero.css + * hero.js + * hero.json +* index.html + */ +import chai from 'chai'; +import fs from 'fs'; +import glob from 'glob-promise'; +import { JSDOM } from 'jsdom'; +import path from 'path'; +import { runSmokeTest } from '../../../../../test/smoke-test.js'; +import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Build Greenwood With: ', function() { + const LABEL = 'ESM Import Attribute for CSS and JSON with prerendering'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(false, true); + }); + + describe(LABEL, function() { + before(function() { + runner.setup(outputPath, getSetupFiles(outputPath)); + runner.runCommand(cliPath, 'build'); + }); + + runSmokeTest(['public'], LABEL); + + describe('Importing CSS w/ Constructable Stylesheets', function() { + let scripts; + + before(async function() { + scripts = await glob.promise(path.join(this.context.publicDir, '*.js')); + }); + + // TODO is this actually the output we want here? + // https://github.com/ProjectEvergreen/greenwood/discussions/1216 + it('should have the expected output from importing hero.css as a Constructable Stylesheet', function() { + const scriptContents = fs.readFileSync(scripts[0], 'utf-8'); + + expect(scriptContents).to.contain('const t=new CSSStyleSheet;t.replaceSync(":host { text-align: center'); + }); + }); + + describe('Importing JSON', function() { + let dom; + + before(async function() { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html')); + }); + + it('should have the expected inline content from the JSON file', function() { + const hero = new JSDOM(dom.window.document.querySelector('app-hero template[shadowrootmode="open"]').innerHTML); + const heading = hero.window.document.querySelectorAll('div.hero h2'); + + expect(heading.length).to.equal(1); + expect(heading[0].textContent).to.be.equal('Welcome to my website'); + }); + }); + }); + + after(function() { + runner.teardown(getOutputTeardownFiles(outputPath)); + }); + +}); \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.prerender-import-attributes/package.json b/packages/cli/test/cases/loaders-build.prerender-import-attributes/package.json new file mode 100644 index 000000000..aead43de3 --- /dev/null +++ b/packages/cli/test/cases/loaders-build.prerender-import-attributes/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.css b/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.css new file mode 100644 index 000000000..86fb17fc3 --- /dev/null +++ b/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.css @@ -0,0 +1,18 @@ +:host { + text-align: center; + margin-bottom: 40px; +} + +:host h2 { + font-size: 3em; +} + +:host button { + display: inline-block; + background-color: var(--color-primary); + color: var(--color-white); + font-size: 1.5em; + padding: 14px; + border-radius: 10px; + cursor: pointer; +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.js b/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.js new file mode 100644 index 000000000..9ef6625cb --- /dev/null +++ b/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.js @@ -0,0 +1,40 @@ +import sheet from './hero.css' with { type: 'css' }; +import data from './hero.json' with { type: 'json' }; + +export default class HeroBanner extends HTMLElement { + clickButton(el) { + console.log('clicked button =>', el.textContent); + } + + connectedCallback() { + if (!this.shadowRoot) { + const template = document.createElement('template'); + + template.innerHTML = ` + <div class="hero"> + <h2>${data.message}</h2> + + <a href="#"> + <button>Get Started</button> + </a> + <a href="#"> + <button>Learn More →</button> + </a> + + </div> + `; + + this.attachShadow({ mode: 'open' }); + this.shadowRoot.appendChild(template.content.cloneNode(true)); + } + + this.shadowRoot.adoptedStyleSheets = [sheet]; + // TODO upstream to WCC? + // this.shadowRoot.querySelectorAll('button') + // .forEach(button => { + // button.addEventListener('click', () => this.clickButton(button)) + // }); + } +} + +customElements.define('app-hero', HeroBanner); \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.json b/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.json new file mode 100644 index 000000000..662f223f5 --- /dev/null +++ b/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/components/hero/hero.json @@ -0,0 +1,3 @@ +{ + "message": "Welcome to my website" +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/index.html b/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/index.html new file mode 100644 index 000000000..f67c1b687 --- /dev/null +++ b/packages/cli/test/cases/loaders-build.prerender-import-attributes/src/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en" prefix="og:http://ogp.me/ns#"> + + <head> + <script type="module" src="./components/hero/hero.js"></script> + </head> + + <body> + <app-hero></app-hero> + </body> + +</html> \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-develop.ssr-import-attributes/loaders-develop.ssr-import-attributes.spec.js b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/loaders-develop.ssr-import-attributes.spec.js new file mode 100644 index 000000000..4e8ba8745 --- /dev/null +++ b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/loaders-develop.ssr-import-attributes.spec.js @@ -0,0 +1,186 @@ +/* + * Use Case + * Run Greenwood develop command with no config. + * + * User Result + * Should start the development server and render a bare bones Greenwood build. + * + * User Command + * greenwood develop + * + * User Config + * {} + * + * User Workspace + * src/ + * api/ + * fragment.js + * components/ + * card/ + * card.css + * card.js + * card.json + * pages/ + * greeting.js + * + */ +import chai from 'chai'; +import { JSDOM } from 'jsdom'; +import path from 'path'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Develop Greenwood With: ', function() { + const LABEL = 'Import Attributes used in API Routes and SSR Pages'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + const hostname = 'http://127.0.0.1:1984'; + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(false, true); + }); + + describe(LABEL, function() { + + before(async function() { + runner.setup(outputPath); + + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 5000); + + runner.runCommand(cliPath, 'develop', { async: true }); + }); + }); + + describe('CSS file is returned as CSS (text/css)', function() { + let response = {}; + let body; + + before(async function() { + response = await fetch(`${hostname}/components/card/card.css`); + body = await response.clone().text(); + }); + + it('should return a 200 status', function(done) { + expect(response.status).to.equal(200); + done(); + }); + + it('should return the correct content type', function(done) { + expect(response.headers.get('content-type')).to.equal('text/css'); + done(); + }); + + it('should return the correct response body', function(done) { + expect(body).to.equal(':host {\n color: red;\n}'); + done(); + }); + }); + + describe('JSON file is returned as JSON (application/json)', function() { + let response = {}; + let data; + + before(async function() { + response = await fetch(`${hostname}/components/card/card.json`); + data = await response.clone().json(); + }); + + it('should return a 200 status', function(done) { + expect(response.status).to.equal(200); + done(); + }); + + it('should return the correct content type', function(done) { + expect(response.headers.get('content-type')).to.equal('application/json'); + done(); + }); + + it('should return the correct response body data', function(done) { + expect(data.image.url).to.equal('/path/to/image.webp'); + done(); + }); + }); + + describe('API Route specific behaviors for an HTML ("fragment") API', function() { + const name = 'Greenwood'; + let response = {}; + let body; + + before(async function() { + response = await fetch(`${hostname}/api/fragment?name=${name}`); + body = await response.clone().text(); + }); + + it('should return a 200 status', function(done) { + expect(response.status).to.equal(200); + done(); + }); + + it('should return the correct content type', function(done) { + expect(response.headers.get('content-type')).to.equal('text/html'); + done(); + }); + + it('should return the correct response body', function(done) { + const dom = new JSDOM(body); + const card = new JSDOM(dom.window.document.querySelectorAll('app-card template[shadowrootmode="open"]')[0].innerHTML); + const heading = card.window.document.querySelector('h2'); + const image = card.window.document.querySelector('img'); + + expect(heading.textContent).to.equal(`Hello, ${name}!`); + expect(image.getAttribute('href')).to.equal('/path/to/image.webp'); + + done(); + }); + }); + + describe('SSR route specific behaviors when using a custom element as the page', function() { + let response = {}; + let body; + + before(async function() { + response = await fetch(`${hostname}/greeting/`); + body = await response.clone().text(); + }); + + it('should return a 200 status', function(done) { + expect(response.status).to.equal(200); + done(); + }); + + it('should return the correct content type', function(done) { + expect(response.headers.get('content-type')).to.equal('text/html'); + done(); + }); + + it('should return the correct response body', function(done) { + const dom = new JSDOM(body); + const card = new JSDOM(dom.window.document.querySelectorAll('app-card template[shadowrootmode="open"]')[0].innerHTML); + const heading = card.window.document.querySelector('h2'); + const image = card.window.document.querySelector('img'); + + expect(heading.textContent).to.equal('Hello, World!'); + expect(image.getAttribute('href')).to.equal('/path/to/image.webp'); + + done(); + }); + }); + }); + + after(function() { + runner.stopCommand(); + runner.teardown([ + path.join(outputPath, '.greenwood'), + path.join(outputPath, 'node_modules') + ]); + }); +}); \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/api/fragment.js b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/api/fragment.js new file mode 100644 index 000000000..a37a035db --- /dev/null +++ b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/api/fragment.js @@ -0,0 +1,17 @@ +import { renderFromHTML } from 'wc-compiler'; + +export async function handler(request) { + const params = new URLSearchParams(request.url.slice(request.url.indexOf('?'))); + const name = params.has('name') ? params.get('name') : 'World'; + const { html } = await renderFromHTML(` + <app-card name="${name}"></app-card> + `, [ + new URL('../components/card/card.js', import.meta.url) + ]); + + return new Response(html, { + headers: new Headers({ + 'Content-Type': 'text/html' + }) + }); +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.css b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.css new file mode 100644 index 000000000..2ace1c18d --- /dev/null +++ b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.css @@ -0,0 +1,3 @@ +:host { + color: red; +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.js b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.js new file mode 100644 index 000000000..49a154086 --- /dev/null +++ b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.js @@ -0,0 +1,27 @@ +import sheet from './card.css' with { type: 'css' }; +import data from './card.json' with { type: 'json' }; + +export default class Card extends HTMLElement { + + connectedCallback() { + if (!this.shadowRoot) { + const name = this.getAttribute('name') || 'World'; + const template = document.createElement('template'); + + template.innerHTML = ` + <div class="card"> + <img alt="logo" href="${data.image.url}"> + <h2>Hello, ${name}!</h2> + </div> + <hr/> + `; + + this.attachShadow({ mode: 'open' }); + this.shadowRoot.appendChild(template.content.cloneNode(true)); + } + + this.shadowRoot.adoptedStyleSheets = [sheet]; + } +} + +customElements.define('app-card', Card); \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.json b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.json new file mode 100644 index 000000000..cf759ab71 --- /dev/null +++ b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/components/card/card.json @@ -0,0 +1,5 @@ +{ + "image": { + "url": "/path/to/image.webp" + } +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/pages/greeting.js b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/pages/greeting.js new file mode 100644 index 000000000..4e69e3014 --- /dev/null +++ b/packages/cli/test/cases/loaders-develop.ssr-import-attributes/src/pages/greeting.js @@ -0,0 +1,10 @@ +import '../components/card/card.js'; + +export default class GreetingPage extends HTMLElement { + + async connectedCallback() { + this.innerHTML = ` + <app-card></app-card> + `; + } +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/loaders-serve.default.ssr-import-attributes.spec.js b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/loaders-serve.default.ssr-import-attributes.spec.js new file mode 100644 index 000000000..816e2ee91 --- /dev/null +++ b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/loaders-serve.default.ssr-import-attributes.spec.js @@ -0,0 +1,167 @@ +/* + * Use Case + * Run Greenwood serve command with no config for using import attributes with API Routes and SSR pages.. + * + * User Result + * Should start the development server and render a bare bones Greenwood build. + * + * User Command + * greenwood serve + * + * User Config + * {} + * + * User Workspace + * src/ + * api/ + * fragment.js + * components/ + * card/ + * card.css + * card.js + * card.json + * pages/ + * greeting.js + * + */ +import chai from 'chai'; +import fs from 'fs'; +import glob from 'glob-promise'; +import { JSDOM } from 'jsdom'; +import path from 'path'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Serve Greenwood With: ', function() { + const LABEL = 'Import Attributes used in API Routes and SSR Pages'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + const hostname = 'http://localhost:8080'; + let runner; + + before(function() { + this.context = { + hostname + }; + runner = new Runner(false, true); + }); + + describe(LABEL, function() { + + before(async function() { + runner.setup(outputPath); + runner.runCommand(cliPath, 'build'); + + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 10000); + + runner.runCommand(cliPath, 'serve', { async: true }); + }); + }); + + describe('API Route specific behaviors for an HTML ("fragment") API', function() { + const name = 'Greenwood'; + let response = {}; + let body; + let scripts; + + before(async function() { + response = await fetch(`${hostname}/api/fragment?name=${name}`); + body = await response.clone().text(); + scripts = await glob.promise(path.join(outputPath, 'public/api/card.*.js')); + }); + + it('should return a 200 status', function(done) { + expect(response.status).to.equal(200); + done(); + }); + + it('should return the correct content type', function(done) { + expect(response.headers.get('content-type')).to.equal('text/html'); + done(); + }); + + it('should return the correct response body', function(done) { + const dom = new JSDOM(body); + const card = new JSDOM(dom.window.document.querySelectorAll('app-card template[shadowrootmode="open"]')[0].innerHTML); + const heading = card.window.document.querySelector('h2'); + const image = card.window.document.querySelector('img'); + + expect(heading.textContent).to.equal(`Hello, ${name}!`); + expect(image.getAttribute('href')).to.equal('/path/to/image.webp'); + + done(); + }); + + it('should have the expected output from importing hero.css as a Constructable Stylesheet', function() { + const scriptContents = fs.readFileSync(scripts[0], 'utf-8'); + + expect(scriptContents).to.contain('const sheet = new CSSStyleSheet();sheet.replaceSync(`:host { color: red; }`);'); + }); + + it('should have the expected output from importing hero.json', function() { + const scriptContents = fs.readFileSync(scripts[0], 'utf-8'); + + expect(scriptContents).to.contain('var data = {"image":{"url":"/path/to/image.webp"}};'); + }); + }); + + describe('SSR route specific behaviors when using a custom element as the page', function() { + let response = {}; + let body; + let scripts; + + before(async function() { + response = await fetch(`${hostname}/greeting/`); + body = await response.clone().text(); + scripts = await glob.promise(path.join(outputPath, 'public/greeting.route.chunk.*.js')); + }); + + it('should return a 200 status', function(done) { + expect(response.status).to.equal(200); + done(); + }); + + it('should return the correct content type', function(done) { + expect(response.headers.get('content-type')).to.equal('text/html'); + done(); + }); + + it('should return the correct response body', function(done) { + const dom = new JSDOM(body); + const card = new JSDOM(dom.window.document.querySelectorAll('app-card template[shadowrootmode="open"]')[0].innerHTML); + const heading = card.window.document.querySelector('h2'); + const image = card.window.document.querySelector('img'); + + expect(heading.textContent).to.equal('Hello, World!'); + expect(image.getAttribute('href')).to.equal('/path/to/image.webp'); + + done(); + }); + + it('should have the expected output from importing hero.css as a Constructable Stylesheet', function() { + const scriptContents = fs.readFileSync(scripts[0], 'utf-8'); + + expect(scriptContents).to.contain('const sheet = new CSSStyleSheet();sheet.replaceSync(`:host { color: red; }`);'); + }); + + it('should have the expected output from importing hero.json', function() { + const scriptContents = fs.readFileSync(scripts[0], 'utf-8'); + + expect(scriptContents).to.contain('var data = {"image":{"url":"/path/to/image.webp"}};'); + }); + }); + }); + + after(function() { + runner.stopCommand(); + runner.teardown([ + path.join(outputPath, '.greenwood'), + path.join(outputPath, 'node_modules') + ]); + }); +}); \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/api/fragment.js b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/api/fragment.js new file mode 100644 index 000000000..a37a035db --- /dev/null +++ b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/api/fragment.js @@ -0,0 +1,17 @@ +import { renderFromHTML } from 'wc-compiler'; + +export async function handler(request) { + const params = new URLSearchParams(request.url.slice(request.url.indexOf('?'))); + const name = params.has('name') ? params.get('name') : 'World'; + const { html } = await renderFromHTML(` + <app-card name="${name}"></app-card> + `, [ + new URL('../components/card/card.js', import.meta.url) + ]); + + return new Response(html, { + headers: new Headers({ + 'Content-Type': 'text/html' + }) + }); +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.css b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.css new file mode 100644 index 000000000..2ace1c18d --- /dev/null +++ b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.css @@ -0,0 +1,3 @@ +:host { + color: red; +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.js b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.js new file mode 100644 index 000000000..49a154086 --- /dev/null +++ b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.js @@ -0,0 +1,27 @@ +import sheet from './card.css' with { type: 'css' }; +import data from './card.json' with { type: 'json' }; + +export default class Card extends HTMLElement { + + connectedCallback() { + if (!this.shadowRoot) { + const name = this.getAttribute('name') || 'World'; + const template = document.createElement('template'); + + template.innerHTML = ` + <div class="card"> + <img alt="logo" href="${data.image.url}"> + <h2>Hello, ${name}!</h2> + </div> + <hr/> + `; + + this.attachShadow({ mode: 'open' }); + this.shadowRoot.appendChild(template.content.cloneNode(true)); + } + + this.shadowRoot.adoptedStyleSheets = [sheet]; + } +} + +customElements.define('app-card', Card); \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.json b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.json new file mode 100644 index 000000000..cf759ab71 --- /dev/null +++ b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/components/card/card.json @@ -0,0 +1,5 @@ +{ + "image": { + "url": "/path/to/image.webp" + } +} \ No newline at end of file diff --git a/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/pages/greeting.js b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/pages/greeting.js new file mode 100644 index 000000000..4e69e3014 --- /dev/null +++ b/packages/cli/test/cases/loaders-serve.default.ssr-import-attributes/src/pages/greeting.js @@ -0,0 +1,10 @@ +import '../components/card/card.js'; + +export default class GreetingPage extends HTMLElement { + + async connectedCallback() { + this.innerHTML = ` + <app-card></app-card> + `; + } +} \ No newline at end of file diff --git a/packages/plugin-babel/src/index.js b/packages/plugin-babel/src/index.js index d4881e915..a3a6b3178 100644 --- a/packages/plugin-babel/src/index.js +++ b/packages/plugin-babel/src/index.js @@ -37,11 +37,11 @@ class BabelResource extends ResourceInterface { this.contentType = ['text/javascript']; } - async shouldIntercept(url) { + async shouldPreIntercept(url) { return url.pathname.split('.').pop() === this.extensions[0] && !url.pathname.startsWith('/node_modules/'); } - async intercept(url, request, response) { + async preIntercept(url, request, response) { const config = await getConfig(this.compilation, this.options.extendConfig); const body = await response.text(); const result = await babel.transform(body, config); diff --git a/packages/plugin-graphql/README.md b/packages/plugin-graphql/README.md index 21f37bc41..243446981 100644 --- a/packages/plugin-graphql/README.md +++ b/packages/plugin-graphql/README.md @@ -45,7 +45,7 @@ This will then allow you to use GraphQL to query your content from your client s ```js import client from '@greenwood/plugin-graphql/src/core/client.js'; -import MenuQuery from '@greenwood/plugin-graphql/src/queries/menu.gql'; +import MenuQuery from '@greenwood/plugin-graphql/src/queries/menu.gql' with { type: 'gql' }; class HeaderComponent extends HTMLElement { constructor() { @@ -167,7 +167,7 @@ query($name: String!) { And then you can use it in your code as such: ```js import client from '@greenwood/plugin-graphql/src/core/client.js'; -import GalleryQuery from '../relative/path/to/data/queries/gallery.gql'; +import GalleryQuery from '../relative/path/to/data/queries/gallery.gql' with { type: 'gql' }; client.query({ query: GalleryQuery, diff --git a/packages/plugin-graphql/test/cases/exp-prerender.query-children/greenwood.config.js b/packages/plugin-graphql/test/cases/loaders-prerender.query-children/greenwood.config.js similarity index 100% rename from packages/plugin-graphql/test/cases/exp-prerender.query-children/greenwood.config.js rename to packages/plugin-graphql/test/cases/loaders-prerender.query-children/greenwood.config.js diff --git a/packages/plugin-graphql/test/cases/exp-prerender.query-children/exp-prerender.query-children.spec.js b/packages/plugin-graphql/test/cases/loaders-prerender.query-children/loaders-prerender.query-children.spec.js similarity index 100% rename from packages/plugin-graphql/test/cases/exp-prerender.query-children/exp-prerender.query-children.spec.js rename to packages/plugin-graphql/test/cases/loaders-prerender.query-children/loaders-prerender.query-children.spec.js diff --git a/packages/plugin-graphql/test/cases/exp-prerender.query-children/package.json b/packages/plugin-graphql/test/cases/loaders-prerender.query-children/package.json similarity index 100% rename from packages/plugin-graphql/test/cases/exp-prerender.query-children/package.json rename to packages/plugin-graphql/test/cases/loaders-prerender.query-children/package.json diff --git a/packages/plugin-graphql/test/cases/exp-prerender.query-children/src/components/posts-list.js b/packages/plugin-graphql/test/cases/loaders-prerender.query-children/src/components/posts-list.js similarity index 95% rename from packages/plugin-graphql/test/cases/exp-prerender.query-children/src/components/posts-list.js rename to packages/plugin-graphql/test/cases/loaders-prerender.query-children/src/components/posts-list.js index 6cf450ec1..32acb60d8 100644 --- a/packages/plugin-graphql/test/cases/exp-prerender.query-children/src/components/posts-list.js +++ b/packages/plugin-graphql/test/cases/loaders-prerender.query-children/src/components/posts-list.js @@ -1,5 +1,5 @@ import client from '@greenwood/plugin-graphql/src/core/client.js'; -import ChildrenQuery from '@greenwood/plugin-graphql/src/queries/children.gql'; +import ChildrenQuery from '@greenwood/plugin-graphql/src/queries/children.gql' with { type: 'gql' }; export default class PostsList extends HTMLElement { async connectedCallback() { diff --git a/packages/plugin-graphql/test/cases/exp-prerender.query-children/src/pages/blog/first-post/index.md b/packages/plugin-graphql/test/cases/loaders-prerender.query-children/src/pages/blog/first-post/index.md similarity index 100% rename from packages/plugin-graphql/test/cases/exp-prerender.query-children/src/pages/blog/first-post/index.md rename to packages/plugin-graphql/test/cases/loaders-prerender.query-children/src/pages/blog/first-post/index.md diff --git a/packages/plugin-graphql/test/cases/exp-prerender.query-children/src/pages/blog/second-post/index.md b/packages/plugin-graphql/test/cases/loaders-prerender.query-children/src/pages/blog/second-post/index.md similarity index 100% rename from packages/plugin-graphql/test/cases/exp-prerender.query-children/src/pages/blog/second-post/index.md rename to packages/plugin-graphql/test/cases/loaders-prerender.query-children/src/pages/blog/second-post/index.md diff --git a/packages/plugin-graphql/test/cases/exp-prerender.query-children/src/pages/index.html b/packages/plugin-graphql/test/cases/loaders-prerender.query-children/src/pages/index.html similarity index 100% rename from packages/plugin-graphql/test/cases/exp-prerender.query-children/src/pages/index.html rename to packages/plugin-graphql/test/cases/loaders-prerender.query-children/src/pages/index.html diff --git a/packages/plugin-import-css/README.md b/packages/plugin-import-css/README.md index 0304e04a0..0b1d603ab 100644 --- a/packages/plugin-import-css/README.md +++ b/packages/plugin-import-css/README.md @@ -1,5 +1,7 @@ # @greenwood/plugin-import-css +> _**THIS PACKAGE HAS BEEN DEPRECATED**_ + ## Overview A Greenwood plugin to allow you use ESM (`import`) syntax to load your CSS. diff --git a/packages/plugin-import-css/package.json b/packages/plugin-import-css/package.json index 15733c402..f56454d1c 100644 --- a/packages/plugin-import-css/package.json +++ b/packages/plugin-import-css/package.json @@ -1,6 +1,7 @@ { "name": "@greenwood/plugin-import-css", "version": "0.30.0-alpha.1", + "private": true, "description": "A Greenwood plugin to allow you to use ESM (import) syntax to load your CSS.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-css", @@ -20,9 +21,6 @@ "files": [ "src/" ], - "publishConfig": { - "access": "public" - }, "peerDependencies": { "@greenwood/cli": "^0.4.0" }, diff --git a/packages/plugin-import-css/test/cases/default/default.spec.js b/packages/plugin-import-css/test/cases/default/default.spec.js index ea53973b3..c8bf0c527 100644 --- a/packages/plugin-import-css/test/cases/default/default.spec.js +++ b/packages/plugin-import-css/test/cases/default/default.spec.js @@ -35,7 +35,7 @@ import { fileURLToPath, URL } from 'url'; const expect = chai.expect; -describe('Build Greenwood With: ', function() { +xdescribe('Build Greenwood With: ', function() { const LABEL = 'Import CSS Plugin with default options'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/plugin-import-css/test/cases/develop.default/develop.default.spec.js b/packages/plugin-import-css/test/cases/develop.default/develop.default.spec.js index 75e21bc9e..c622637c0 100644 --- a/packages/plugin-import-css/test/cases/develop.default/develop.default.spec.js +++ b/packages/plugin-import-css/test/cases/develop.default/develop.default.spec.js @@ -30,7 +30,7 @@ import { runSmokeTest } from '../../../../../test/smoke-test.js'; const expect = chai.expect; -describe('Develop Greenwood With: ', function() { +xdescribe('Develop Greenwood With: ', function() { const LABEL = 'Import CSS plugin for using ESM with .css files'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/plugin-import-css/test/cases/exp-build.prerender/exp-build.prerender.spec.js b/packages/plugin-import-css/test/cases/exp-build.prerender/exp-build.prerender.spec.js index ff1a89771..659ffc324 100644 --- a/packages/plugin-import-css/test/cases/exp-build.prerender/exp-build.prerender.spec.js +++ b/packages/plugin-import-css/test/cases/exp-build.prerender/exp-build.prerender.spec.js @@ -39,7 +39,7 @@ import { fileURLToPath, URL } from 'url'; const expect = chai.expect; -describe('(Experimental) Build Greenwood With: ', function() { +xdescribe('(Experimental) Build Greenwood With: ', function() { const LABEL = 'Import CSS Plugin with static pre-rendering'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/plugin-import-css/test/cases/exp-serve.ssr/exp-serve.ssr.spec.js b/packages/plugin-import-css/test/cases/exp-serve.ssr/exp-serve.ssr.spec.js index f6a59d218..63bd0373d 100644 --- a/packages/plugin-import-css/test/cases/exp-serve.ssr/exp-serve.ssr.spec.js +++ b/packages/plugin-import-css/test/cases/exp-serve.ssr/exp-serve.ssr.spec.js @@ -38,7 +38,7 @@ import { fileURLToPath } from 'url'; const expect = chai.expect; -describe('Serve Greenwood With: ', function() { +xdescribe('Serve Greenwood With: ', function() { const LABEL = 'A Server Rendered Application (SSR) with API Routes importing CSS'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/plugin-import-json/README.md b/packages/plugin-import-json/README.md index d46f15169..d9b9cd077 100644 --- a/packages/plugin-import-json/README.md +++ b/packages/plugin-import-json/README.md @@ -1,5 +1,7 @@ # @greenwood/plugin-import-json +> _**THIS PACKAGE HAS BEEN DEPRECATED**_ + ## Overview A Greenwood plugin to allow you use ESM (`import`) syntax to load your JSON. diff --git a/packages/plugin-import-json/package.json b/packages/plugin-import-json/package.json index dfc168f34..4083ef1a0 100644 --- a/packages/plugin-import-json/package.json +++ b/packages/plugin-import-json/package.json @@ -1,6 +1,7 @@ { "name": "@greenwood/plugin-import-json", "version": "0.30.0-alpha.1", + "private": true, "description": "A Greenwood plugin to allow you to use ESM (import) syntax to load your JSON.", "repository": "https://github.com/ProjectEvergreen/greenwood", "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-json", @@ -20,9 +21,6 @@ "files": [ "src/" ], - "publishConfig": { - "access": "public" - }, "peerDependencies": { "@greenwood/cli": "^0.12.3" }, diff --git a/packages/plugin-import-json/test/cases/default/default.spec.js b/packages/plugin-import-json/test/cases/default/default.spec.js index fb168439a..dfd8a1aeb 100644 --- a/packages/plugin-import-json/test/cases/default/default.spec.js +++ b/packages/plugin-import-json/test/cases/default/default.spec.js @@ -37,7 +37,7 @@ import { fileURLToPath, URL } from 'url'; const expect = chai.expect; -describe('Build Greenwood With: ', function() { +xdescribe('Build Greenwood With: ', function() { const LABEL = 'Import JSON Plugin with default options'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/plugin-import-json/test/cases/develop.default/develop.default.spec.js b/packages/plugin-import-json/test/cases/develop.default/develop.default.spec.js index a0b298cdc..b96adc6c1 100644 --- a/packages/plugin-import-json/test/cases/develop.default/develop.default.spec.js +++ b/packages/plugin-import-json/test/cases/develop.default/develop.default.spec.js @@ -31,7 +31,7 @@ import { runSmokeTest } from '../../../../../test/smoke-test.js'; const expect = chai.expect; -describe('Develop Greenwood With: ', function() { +xdescribe('Develop Greenwood With: ', function() { const LABEL = 'Import JSON plugin for using ESM with .json files'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/plugin-import-json/test/cases/exp-build.prerender/exp-build.prerender.spec.js b/packages/plugin-import-json/test/cases/exp-build.prerender/exp-build.prerender.spec.js index c33d8f616..28bd48bff 100644 --- a/packages/plugin-import-json/test/cases/exp-build.prerender/exp-build.prerender.spec.js +++ b/packages/plugin-import-json/test/cases/exp-build.prerender/exp-build.prerender.spec.js @@ -39,7 +39,7 @@ import { fileURLToPath, URL } from 'url'; const expect = chai.expect; -describe('(Experimental) Build Greenwood With: ', function() { +xdescribe('(Experimental) Build Greenwood With: ', function() { const LABEL = 'Import JSON Plugin with static pre-rendering'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/plugin-import-json/test/cases/exp-serve.ssr/exp-serve.ssr.spec.js b/packages/plugin-import-json/test/cases/exp-serve.ssr/exp-serve.ssr.spec.js index 770c4c163..bc4b70af5 100644 --- a/packages/plugin-import-json/test/cases/exp-serve.ssr/exp-serve.ssr.spec.js +++ b/packages/plugin-import-json/test/cases/exp-serve.ssr/exp-serve.ssr.spec.js @@ -35,7 +35,7 @@ import { fileURLToPath } from 'url'; const expect = chai.expect; -describe('Serve Greenwood With: ', function() { +xdescribe('Serve Greenwood With: ', function() { const LABEL = 'A Server Rendered Application (SSR) with API Routes importing JSON'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/plugin-import-jsx/README.md b/packages/plugin-import-jsx/README.md index 9de639612..f9dab1e06 100644 --- a/packages/plugin-import-jsx/README.md +++ b/packages/plugin-import-jsx/README.md @@ -51,11 +51,10 @@ export default class FooterComponent extends HTMLElement { customElements.define('app-footer', FooterComponent); ``` -A couple notes: +### Notes + - For SSR and `prerender` use cases, [follow these steps](/docs/server-rendering/#custom-imports-experimental) -- For client side / browser code specifically, it is recommended to append `?type=jsx`, e.g. +- For client side / browser code specifically, it is recommended to use import attributes syntax, e.g. ```js - import '../path/to/footer.jsx?type=jsx'; - ``` - -> _The plan is to coalesce around [import assertions](https://github.com/ProjectEvergreen/greenwood/issues/923) in time for the v1.0 release so the same standard syntax can be used on the client and the server._ \ No newline at end of file + import '../path/to/footer.jsx' with { type: 'jsx' }; + ``` \ No newline at end of file diff --git a/packages/plugin-import-jsx/package.json b/packages/plugin-import-jsx/package.json index b51e9d4cb..6c8803ac3 100644 --- a/packages/plugin-import-jsx/package.json +++ b/packages/plugin-import-jsx/package.json @@ -27,7 +27,7 @@ "@greenwood/cli": "^0.28.0-alpha.4" }, "dependencies": { - "wc-compiler": "~0.12.1" + "wc-compiler": "~0.13.0" }, "devDependencies": { "@greenwood/cli": "^0.30.0-alpha.1" diff --git a/packages/plugin-import-jsx/src/index.js b/packages/plugin-import-jsx/src/index.js index ac02dc6c4..36b678721 100644 --- a/packages/plugin-import-jsx/src/index.js +++ b/packages/plugin-import-jsx/src/index.js @@ -15,13 +15,14 @@ class ImportJsxResource extends ResourceInterface { } async shouldServe(url) { - const { pathname } = url; + const { pathname, protocol } = url; + const ext = pathname.split('.').pop(); - return pathname.split('.').pop() === this.extensions[0] && (url.searchParams.has('type') && url.searchParams.get('type') === this.extensions[0]); + return protocol === 'file:' && ext === this.extensions[0]; } async serve(url) { - // TODO refactor when WCC refactors + // refactor when WCC refactors // https://github.com/ProjectEvergreen/wcc/issues/116 const tree = parseJsx(url); const result = escodegen.generate(tree); diff --git a/packages/plugin-import-jsx/test/cases/default/src/templates/app.html b/packages/plugin-import-jsx/test/cases/default/src/templates/app.html index 970f7016c..2d99e73e2 100644 --- a/packages/plugin-import-jsx/test/cases/default/src/templates/app.html +++ b/packages/plugin-import-jsx/test/cases/default/src/templates/app.html @@ -1,7 +1,7 @@ <html> <head> <title>My Personal Website - + diff --git a/packages/plugin-import-jsx/test/cases/exp-build.prerender/greenwood.config.js b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/greenwood.config.js similarity index 100% rename from packages/plugin-import-jsx/test/cases/exp-build.prerender/greenwood.config.js rename to packages/plugin-import-jsx/test/cases/loaders-build.prerender/greenwood.config.js diff --git a/packages/plugin-import-jsx/test/cases/exp-build.prerender/exp-build.prerender.spec.js b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js similarity index 97% rename from packages/plugin-import-jsx/test/cases/exp-build.prerender/exp-build.prerender.spec.js rename to packages/plugin-import-jsx/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js index dc6914c57..bcb0e615c 100644 --- a/packages/plugin-import-jsx/test/cases/exp-build.prerender/exp-build.prerender.spec.js +++ b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js @@ -27,6 +27,7 @@ * index.md * templates/ * app.html + * main.js */ import chai from 'chai'; import glob from 'glob-promise'; @@ -39,7 +40,7 @@ import { fileURLToPath, URL } from 'url'; const expect = chai.expect; -describe('(Experimental) Build Greenwood With: ', function() { +describe('Build Greenwood With: ', function() { const LABEL = 'Import JSX Plugin with static pre-rendering'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); diff --git a/packages/plugin-import-jsx/test/cases/exp-build.prerender/package.json b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/package.json similarity index 100% rename from packages/plugin-import-jsx/test/cases/exp-build.prerender/package.json rename to packages/plugin-import-jsx/test/cases/loaders-build.prerender/package.json diff --git a/packages/plugin-import-jsx/test/cases/exp-build.prerender/src/components/footer.jsx b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/components/footer.jsx similarity index 100% rename from packages/plugin-import-jsx/test/cases/exp-build.prerender/src/components/footer.jsx rename to packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/components/footer.jsx diff --git a/packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/main.js b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/main.js new file mode 100644 index 000000000..52c14ef72 --- /dev/null +++ b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/main.js @@ -0,0 +1 @@ +import './components/footer.jsx' with { type: 'jsx' }; \ No newline at end of file diff --git a/packages/plugin-import-jsx/test/cases/exp-build.prerender/src/pages/index.md b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/pages/index.md similarity index 100% rename from packages/plugin-import-jsx/test/cases/exp-build.prerender/src/pages/index.md rename to packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/pages/index.md diff --git a/packages/plugin-import-jsx/test/cases/exp-build.prerender/src/templates/app.html b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/templates/app.html similarity index 61% rename from packages/plugin-import-jsx/test/cases/exp-build.prerender/src/templates/app.html rename to packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/templates/app.html index b17ccc360..636405140 100644 --- a/packages/plugin-import-jsx/test/cases/exp-build.prerender/src/templates/app.html +++ b/packages/plugin-import-jsx/test/cases/loaders-build.prerender/src/templates/app.html @@ -1,7 +1,7 @@ My Personal Website - + diff --git a/packages/plugin-import-raw/README.md b/packages/plugin-import-raw/README.md new file mode 100644 index 000000000..61c33e867 --- /dev/null +++ b/packages/plugin-import-raw/README.md @@ -0,0 +1,63 @@ +# @greenwood/plugin-import-raw + +## Overview + +A Greenwood plugin to use ESM (`import`) syntax to load any file contents as a string exported as a JavaScript module. Inspired by **webpack**'s [raw loader](https://v4.webpack.js.org/loaders/raw-loader/). + +> This package assumes you already have `@greenwood/cli` installed. + +## Installation +You can use your favorite JavaScript package manager to install this package. + +_examples:_ +```bash +# npm +npm install @greenwood/plugin-import-raw --save-dev + +# yarn +yarn add @greenwood/plugin-import-raw --dev +``` + +## Usage +Add this plugin to your _greenwood.config.js_ and spread the `export`. + +```javascript +import { greenwoodPluginImportRaw } from '@greenwood/plugin-import-raw'; + +export default { + // ... + + plugins: [ + greenwoodPluginImportRaw() + ] +} +``` + +This will then allow you to use ESM (`import`) to include any file as an arbitrary string exported as a JavaScript module. +```js +import css from '../path/to/styles.css?type=raw'; // must be a relative path per ESM spec + +console.log(css); // h1 { color: red } +``` + +> For libraries like Material Web Components, this plugin will [resolve references to _some-file.css_ if the equivalent exists that ends in _.js_ (e.g. _styles.css.js_)](https://github.com/ProjectEvergreen/greenwood/issues/700). + +## Options + +### Matches + +Optionally, you can provide an array of "matcher" patterns for the plugin to transform custom paths, which can be useful for handling imports you can't change, like third party files in _node_modules_. + +```javascript +import { greenwoodPluginImportRaw } from '@greenwood/plugin-import-raw'; + +export default { + plugins: [ + greenwoodPluginImportRaw({ + matches: [ + '/node_modules/some-package/dist/styles.css' + ] + }) + ] +} +``` \ No newline at end of file diff --git a/packages/plugin-import-raw/package.json b/packages/plugin-import-raw/package.json new file mode 100644 index 000000000..c6d8a568c --- /dev/null +++ b/packages/plugin-import-raw/package.json @@ -0,0 +1,31 @@ +{ + "name": "@greenwood/plugin-import-raw", + "version": "0.30.0-alpha.1", + "description": "A Greenwood plugin to allow you to use ESM (import) syntax to load any file content as a string.", + "repository": "https://github.com/ProjectEvergreen/greenwood", + "homepage": "https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-raw", + "author": "Owen Buckley ", + "license": "MIT", + "keywords": [ + "Greenwood", + "Static Site Generator", + "Full Stack Web Development", + "Web Components", + "NodeJS", + "ESM" + ], + "main": "src/index.js", + "type": "module", + "files": [ + "src/" + ], + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "@greenwood/cli": "^0.4.0" + }, + "devDependencies": { + "@greenwood/cli": "^0.30.0-alpha.1" + } +} diff --git a/packages/plugin-import-raw/src/index.js b/packages/plugin-import-raw/src/index.js new file mode 100644 index 000000000..499b7e382 --- /dev/null +++ b/packages/plugin-import-raw/src/index.js @@ -0,0 +1,67 @@ +/* + * + * Enables using JavaScript to import any type of file as a string using ESM syntax. + * + */ +import { ResourceInterface } from '@greenwood/cli/src/lib/resource-interface.js'; + +class ImportRawResource extends ResourceInterface { + constructor(compilation, options) { + super(compilation, options); + + this.contentType = 'text/javascript'; + } + + async shouldResolve(url) { + const matches = (this.options.matches || []).filter(matcher => url.href.indexOf(matcher) >= 0); + + if (matches.length > 0 && !url.searchParams.has('type')) { + return true; + } + } + + async resolve(url) { + const { projectDirectory } = this.compilation.context; + const { pathname, searchParams } = url; + const params = url.searchParams.size > 0 + ? `${searchParams.toString()}&type=raw` + : 'type=raw'; + const root = pathname.startsWith('file://') + ? new URL(`file://${pathname}`).href + : pathname.startsWith('/node_modules') + ? new URL(`.${pathname}`, projectDirectory).href + : new URL(`file://${pathname}`); + const matchedUrl = new URL(`${root}?${params}`); + + return new Request(matchedUrl); + } + + async shouldIntercept(url, request) { + const matches = (this.options.matches || []).filter(matcher => url.href.indexOf(matcher) >= 0); + const type = url.searchParams.get('type'); + const dest = request.headers.get('Sec-Fetch-Dest'); + + return (url.protocol === 'file:' && type === 'raw' && dest === 'empty') || matches.length > 0; + } + + async intercept(url, request, response) { + const body = await response.text(); + const contents = `const raw = \`${body.replace(/\r?\n|\r/g, ' ').replace(/\\/g, '\\\\')}\`;\nexport default raw;`; + + return new Response(contents, { + headers: new Headers({ + 'Content-Type': this.contentType + }) + }); + } +} + +const greenwoodPluginImportRaw = (options = {}) => { + return [{ + type: 'resource', + name: 'plugin-import-raw:resource', + provider: (compilation) => new ImportRawResource(compilation, options) + }]; +}; + +export { greenwoodPluginImportRaw }; \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/build.matchers/build.matchers.spec.js b/packages/plugin-import-raw/test/cases/build.matchers/build.matchers.spec.js new file mode 100644 index 000000000..8580fe09b --- /dev/null +++ b/packages/plugin-import-raw/test/cases/build.matchers/build.matchers.spec.js @@ -0,0 +1,84 @@ +/* + * Use Case + * Run Greenwood and be able to load arbitrary content as a string using ESM. + * + * User Result + * Should generate a bare bones Greenwood build without erroring when using ESM (import) as a string value. + * + * User Command + * greenwood build + * + * User Config + * import { greenwoodPluginImportRaw } from '@greenwood/plugin-import-raw'; + * + * { + * plugins: [{ + * greenwoodPluginImportRaw({ + * matches: [ + * 'eve-container.css' + * ] + * }) + * }] + * } + * + * User Workspace + * src/ + * pages/ + * index.html + * main.js + */ +import chai from 'chai'; +import fs from 'fs'; +import glob from 'glob-promise'; +import path from 'path'; +import { runSmokeTest } from '../../../../../test/smoke-test.js'; +import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Build Greenwood With: ', function() { + const LABEL = 'Import Raw Plugin with default options'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(); + }); + + describe(LABEL, function() { + before(function() { + runner.setup(outputPath, getSetupFiles(outputPath)); + runner.runCommand(cliPath, 'build'); + }); + + runSmokeTest(['public', 'index'], LABEL); + + describe('Importing a string of CSS using ESM (import)', function() { + let scripts; + + before(async function() { + scripts = await glob.promise(path.join(this.context.publicDir, '*.js')); + }); + + it('should contain one (CSS-in) JavaScript file in the output directory', function() { + expect(scripts.length).to.be.equal(1); + }); + + it('should have the expected output from importing styles.css in main.js', function() { + const contents = fs.readFileSync(scripts[0], 'utf-8'); + + expect(contents).to.contain('console.log({css:"@custom-media --screen-xs'); + }); + }); + }); + + after(function() { + runner.teardown(getOutputTeardownFiles(outputPath)); + }); +}); \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/build.matchers/greenwood.config.js b/packages/plugin-import-raw/test/cases/build.matchers/greenwood.config.js new file mode 100644 index 000000000..0cc263059 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/build.matchers/greenwood.config.js @@ -0,0 +1,12 @@ +import { greenwoodPluginImportRaw } from '../../../src/index.js'; + +export default { + + plugins: [ + greenwoodPluginImportRaw({ + matches: [ + 'eve-container.css' + ] + }) + ] +}; \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/build.matchers/src/main.js b/packages/plugin-import-raw/test/cases/build.matchers/src/main.js new file mode 100644 index 000000000..e90390161 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/build.matchers/src/main.js @@ -0,0 +1,3 @@ +import css from '@evergreen-wc/eve-container/src/eve-container.css'; + +console.log({ css }); \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/build.matchers/src/pages/index.html b/packages/plugin-import-raw/test/cases/build.matchers/src/pages/index.html new file mode 100644 index 000000000..e58731458 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/build.matchers/src/pages/index.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/default/default.spec.js b/packages/plugin-import-raw/test/cases/default/default.spec.js new file mode 100644 index 000000000..3aa11e5e8 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/default/default.spec.js @@ -0,0 +1,83 @@ +/* + * Use Case + * Run Greenwood and be able to load arbitrary content as a string using ESM. + * + * User Result + * Should generate a bare bones Greenwood build without erroring when using ESM (import) as a string value. + * + * User Command + * greenwood build + * + * User Config + * import { greenwoodPluginImportRaw } from '@greenwood/plugin-import-raw'; + * + * { + * plugins: [{ + * greenwoodPluginImportRaw() + * }] + * } + * + * User Workspace + * src/ + * pages/ + * index.html + * main.js + * styles.css + */ +import chai from 'chai'; +import fs from 'fs'; +import glob from 'glob-promise'; +import path from 'path'; +import { runSmokeTest } from '../../../../../test/smoke-test.js'; +import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Build Greenwood With: ', function() { + const LABEL = 'Import Raw Plugin with default options'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(); + }); + + describe(LABEL, function() { + before(function() { + runner.setup(outputPath, getSetupFiles(outputPath)); + runner.runCommand(cliPath, 'build'); + }); + + runSmokeTest(['public', 'index'], LABEL); + + describe('Importing a string of CSS using ESM (import)', function() { + let scripts; + + before(async function() { + scripts = await glob.promise(path.join(this.context.publicDir, '*.js')); + }); + + it('should contain one (CSS-in) JavaScript file in the output directory', function() { + expect(scripts.length).to.be.equal(1); + }); + + it('should have the expected output from importing styles.css in main.js', function() { + const contents = fs.readFileSync(scripts[0], 'utf-8'); + + // TODO minify CSS-in-JS? + expect(contents).to.contain('import from styles.css: p { color: red; }"'); + }); + }); + }); + + after(function() { + runner.teardown(getOutputTeardownFiles(outputPath)); + }); + +}); \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/default/greenwood.config.js b/packages/plugin-import-raw/test/cases/default/greenwood.config.js new file mode 100644 index 000000000..b1c45a9a5 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/default/greenwood.config.js @@ -0,0 +1,8 @@ +import { greenwoodPluginImportRaw } from '../../../src/index.js'; + +export default { + + plugins: [ + greenwoodPluginImportRaw() + ] +}; \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/default/src/main.js b/packages/plugin-import-raw/test/cases/default/src/main.js new file mode 100644 index 000000000..a60786bca --- /dev/null +++ b/packages/plugin-import-raw/test/cases/default/src/main.js @@ -0,0 +1,3 @@ +import stylesCss from './styles.css?type=raw'; + +document.getElementsByTagName('span')[0].innerHTML = `import from styles.css: ${stylesCss}`; \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/default/src/pages/index.html b/packages/plugin-import-raw/test/cases/default/src/pages/index.html new file mode 100644 index 000000000..e58731458 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/default/src/pages/index.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/default/src/styles.css b/packages/plugin-import-raw/test/cases/default/src/styles.css new file mode 100644 index 000000000..e21e2c285 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/default/src/styles.css @@ -0,0 +1,3 @@ +p { + color: red; +} \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/develop.default/develop.default.spec.js b/packages/plugin-import-raw/test/cases/develop.default/develop.default.spec.js new file mode 100644 index 000000000..eb99f4e35 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/develop.default/develop.default.spec.js @@ -0,0 +1,126 @@ +/* + * Use Case + * Run Greenwood develop command with no raw plugin. + * + * User Result + * Should start the development server and render a bare bones Greenwood build and return CSS file as a string using ESM. + * + * User Command + * greenwood develop + * + * User Config + * import { greenwoodPluginImportRaw } from '@greenwood/plugin-import-raw'; + * + * { + * plugins: [{ + * greenwoodPluginImportRaw() + * }] + * } + * + * User Workspace + * src/ + * main.css + * style.css.js + * + */ +import chai from 'chai'; +import path from 'path'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; +import { runSmokeTest } from '../../../../../test/smoke-test.js'; + +const expect = chai.expect; + +describe('Develop Greenwood With: ', function() { + const LABEL = 'Import Raw plugin for using ESM with arbitrary files as strings'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + const hostname = 'http://localhost'; + const port = 1984; + let runner; + + before(function() { + this.context = { + hostname: `${hostname}:${port}` + }; + runner = new Runner(); + }); + + describe(LABEL, function() { + + before(async function() { + runner.setup(outputPath); + + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 5000); + + runner.runCommand(cliPath, 'develop', { async: true }); + }); + }); + + runSmokeTest(['serve'], LABEL); + + describe('Develop command with raw ESM behaviors with CSS', function() { + let response = {}; + let data; + + before(async function() { + response = await fetch(`${hostname}:${port}/main.css?type=raw`, { + headers: { + 'Sec-Fetch-Dest': 'empty' + } + }); + data = await response.text(); + }); + + it('should return a 200', function() { + expect(response.status).to.equal(200); + }); + + it('should return the correct content type', function() { + expect(response.headers.get('content-type')).to.equal('text/javascript'); + }); + + // https://github.com/ProjectEvergreen/greenwood/issues/766 + // https://unpkg.com/browse/bootstrap@4.6.1/dist/css/bootstrap.css + // https://unpkg.com/browse/font-awesome@4.7.0/css/font-awesome.css + it('should return an ECMASCript module', function() { + expect(data.replace('\n', '').replace(/ /g, '').trim()) + .to.equal('constraw=`*{background-image:url("/assets/background.jpg");font-family:\'Arial\'}.blockquote-footer::before{content:"\\\\2014\\\\00A0";}.fa-chevron-right:before{content:"\\\\f054";}`;exportdefaultraw;'); // eslint-disable-line max-len + }); + }); + + // https://github.com/ProjectEvergreen/greenwood/pull/747 + // https://unpkg.com/browse/@material/mwc-button@0.22.1/styles.css.js + xdescribe('Develop command for .css.js files behaviors (CSS in disguise)', function() { + let response = {}; + let data; + + before(async function() { + response = await fetch(`${hostname}:${port}/styles.css.js`); + data = await response.text(); + }); + + it('should return a 200', function() { + expect(response.status).to.equal(200); + }); + + it('should return the correct content type', function() { + expect(response.headers.get('content-type')).to.equal('text/javascript'); + }); + + it('should return an ECMASCript module', function() { + expect(data).to.equal('export const styles = css `.mdc-touch-target-wrapper{display:inline}`;'); + }); + }); + }); + + after(function() { + runner.stopCommand(); + runner.teardown([ + path.join(outputPath, '.greenwood') + ]); + }); +}); \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/develop.default/greenwood.config.js b/packages/plugin-import-raw/test/cases/develop.default/greenwood.config.js new file mode 100644 index 000000000..da910c5a6 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/develop.default/greenwood.config.js @@ -0,0 +1,7 @@ +import { greenwoodPluginImportRaw } from '../../../src/index.js'; + +export default { + plugins: [ + greenwoodPluginImportRaw() + ] +}; \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/develop.default/package.json b/packages/plugin-import-raw/test/cases/develop.default/package.json new file mode 100644 index 000000000..1105aa77c --- /dev/null +++ b/packages/plugin-import-raw/test/cases/develop.default/package.json @@ -0,0 +1,4 @@ +{ + "name": "test-plugin-import-raw-css-develop-default", + "type": "module" +} \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/develop.default/src/main.css b/packages/plugin-import-raw/test/cases/develop.default/src/main.css new file mode 100644 index 000000000..f5ae38ad4 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/develop.default/src/main.css @@ -0,0 +1,12 @@ +* { + background-image: url("/assets/background.jpg"); + font-family: 'Arial' +} + +.blockquote-footer::before { + content: "\2014\00A0"; +} + +.fa-chevron-right:before { + content: "\f054"; +} \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/develop.default/src/styles.css.js b/packages/plugin-import-raw/test/cases/develop.default/src/styles.css.js new file mode 100644 index 000000000..12bcd6b16 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/develop.default/src/styles.css.js @@ -0,0 +1 @@ +export const styles = css `.mdc-touch-target-wrapper{display:inline}`; \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-build.prerender/greenwood.config.js b/packages/plugin-import-raw/test/cases/loaders-build.prerender/greenwood.config.js new file mode 100644 index 000000000..e7c9f2d37 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-build.prerender/greenwood.config.js @@ -0,0 +1,8 @@ +import { greenwoodPluginImportRaw } from '../../../src/index.js'; + +export default { + prerender: true, + plugins: [ + greenwoodPluginImportRaw() + ] +}; \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js b/packages/plugin-import-raw/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js new file mode 100644 index 000000000..ccbb5efe8 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js @@ -0,0 +1,90 @@ +/* + * Use Case + * Run Greenwood with a plugin during prerendering to be able to import arbitrary text as a string using ESM. + * + * User Result + * Should generate a static Greenwood build with CSS properly prerendered. + * + * User Command + * greenwood build + * + * User Config + * import { greenwoodPluginImportRaw } from '@greenwood/plugin-import-raw'; + * + * { + * prerender: true, + * plugins: [{ + * greenwoodPluginImportRaw() + * }] + * } + * + * User Workspace + * src/ + * components/ + * footer.css + * footer.js + * pages/ + * index.md + * templates/ + * app.html + */ +import chai from 'chai'; +import glob from 'glob-promise'; +import { JSDOM } from 'jsdom'; +import path from 'path'; +import { runSmokeTest } from '../../../../../test/smoke-test.js'; +import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Build Greenwood With: ', function() { + const LABEL = 'Import Raw Plugin with static pre-rendering for CSS as a string'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(false, true); + }); + + describe(LABEL, function() { + before(function() { + runner.setup(outputPath, getSetupFiles(outputPath)); + runner.runCommand(cliPath, 'build'); + }); + + runSmokeTest(['public'], LABEL); + + describe('Importing CSS as a string using ESM (import)', function() { + let dom; + let scripts; + + before(async function() { + scripts = await glob.promise(path.join(this.context.publicDir, '*.js')); + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html')); + }); + + it('should contain no (CSS-in) JavaScript file in the output directory', function() { + expect(scripts.length).to.be.equal(0); + }); + + it('should have the expected output from importing styles.css in index.html', function() { + const styles = dom.window.document.querySelectorAll('style'); + + // TODO minify CSS-in-JS? + expect(styles.length).to.equal(1); + expect(styles[0].textContent).to.contain('.footer { width: 90%; margin: 0 auto; padding: 0; text-align: center; }'); + }); + }); + }); + + after(function() { + runner.teardown(getOutputTeardownFiles(outputPath)); + }); + +}); \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-build.prerender/package.json b/packages/plugin-import-raw/test/cases/loaders-build.prerender/package.json new file mode 100644 index 000000000..606abcb6a --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-build.prerender/package.json @@ -0,0 +1,4 @@ +{ + "name": "test-plugin-import-raw-css-build-prerender", + "type": "module" +} \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/components/footer.css b/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/components/footer.css new file mode 100644 index 000000000..9141c1418 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/components/footer.css @@ -0,0 +1 @@ +.footer { width: 90%; margin: 0 auto; padding: 0; text-align: center; } \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/components/footer.js b/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/components/footer.js new file mode 100644 index 000000000..388ad54f6 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/components/footer.js @@ -0,0 +1,25 @@ +import css from './footer.css?type=raw'; + +export default class FooterComponent extends HTMLElement { + connectedCallback() { + this.innerHTML = this.getTemplate(); + } + + getTemplate() { + const year = new Date().getFullYear(); + + return ` + + + + `; + } +} + +customElements.define('app-footer', FooterComponent); \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/pages/index.md b/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/pages/index.md new file mode 100644 index 000000000..82c330a89 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/pages/index.md @@ -0,0 +1,3 @@ +# Home Page + +Welcome to the home page! \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/templates/app.html b/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/templates/app.html new file mode 100644 index 000000000..7c394fe4b --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-build.prerender/src/templates/app.html @@ -0,0 +1,12 @@ + + + My Personal Website + + + + + + + + + \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-serve.ssr/greenwood.config.js b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/greenwood.config.js new file mode 100644 index 000000000..da910c5a6 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/greenwood.config.js @@ -0,0 +1,7 @@ +import { greenwoodPluginImportRaw } from '../../../src/index.js'; + +export default { + plugins: [ + greenwoodPluginImportRaw() + ] +}; \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-serve.ssr/loaders-serve.ssr.spec.js b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/loaders-serve.ssr.spec.js new file mode 100644 index 000000000..9c6c84a5e --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/loaders-serve.ssr.spec.js @@ -0,0 +1,152 @@ +/* + * Use Case + * Run Greenwood with an API and SSR routes that import raw CSS as a string. + * + * User Result + * Should generate a Greenwood build that correctly builds and bundles all assets. + * + * User Command + * greenwood build + * + * User Config + * { + * plugins: [ + * greenwoodPluginImportRaw() + * ] + * } + * + * User Workspace + * src/ + * api/ + * fragment.js + * components/ + * card.js + * card.css + * pages/ + * products.js + * services/ + * products.js + * styles/ + * some.css + */ +import chai from 'chai'; +import { JSDOM } from 'jsdom'; +import path from 'path'; +import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; +import { Runner } from 'gallinago'; +import { fileURLToPath } from 'url'; + +const expect = chai.expect; + +describe('Serve Greenwood With: ', function() { + const LABEL = 'A Server Rendered Application (SSR) with API Routes importing raw CSS'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + const hostname = 'http://localhost:8080'; + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public'), + hostname + }; + runner = new Runner(false, true); + }); + + describe(LABEL, function() { + + before(async function() { + runner.setup(outputPath, getSetupFiles(outputPath)); + runner.runCommand(cliPath, 'build'); + + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 10000); + + runner.runCommand(cliPath, 'serve', { async: true }); + }); + }); + + describe('Serve command with HTML route response for products page', function() { + let response = {}; + let data; + let productsPageDom; + + before(async function() { + response = await fetch(`${hostname}/products/`); + data = await response.text(); + productsPageDom = new JSDOM(data); + }); + + it('should return a 200 status', function() { + expect(response.status).to.equal(200); + }); + + it('should return the correct content type', function() { + expect(response.headers.get('content-type')).to.equal('text/html'); + }); + + it('should return a response body', function() { + expect(data).to.not.be.undefined; + }); + + it('should have the expected import CSS in the page in the response body', function(done) { + const styleTag = productsPageDom.window.document.querySelectorAll('body > style'); + + expect(styleTag.length).to.equal(1); + expect(styleTag[0].textContent.replace(/ /g, '').replace(/\n/, '')).contain('h1{color:red;}'); + done(); + }); + + it('should make sure to have the expected CSS inlined into the page for each ', function(done) { + const cardComponents = productsPageDom.window.document.querySelectorAll('body app-card'); + + expect(cardComponents.length).to.equal(2); + Array.from(cardComponents).forEach((card) => { + expect(card.innerHTML).contain('display: flex;'); + }); + done(); + }); + }); + + describe('Serve command with API specific behaviors for an HTML ("fragment") API', function() { + let response = {}; + let fragmentsApiDom; + + before(async function() { + response = await fetch(`${hostname}/api/fragment`); + const body = await response.clone().text(); + fragmentsApiDom = new JSDOM(body); + }); + + it('should return a 200 status', function() { + expect(response.status).to.equal(200); + }); + + it('should return a custom status message', function() { + expect(response.statusText).to.equal('OK'); + }); + + it('should return the correct content type', function() { + expect(response.headers.get('content-type')).to.equal('text/html'); + }); + + it('should make sure to have the expected CSS inlined into the page for each ', function(done) { + const cardComponents = fragmentsApiDom.window.document.querySelectorAll('body > app-card'); + + expect(cardComponents.length).to.equal(2); + Array.from(cardComponents).forEach((card) => { + expect(card.innerHTML).contain('display: flex;'); + }); + done(); + }); + }); + }); + + after(function() { + runner.teardown(getOutputTeardownFiles(outputPath)); + runner.stopCommand(); + }); + +}); \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-serve.ssr/package.json b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/package.json new file mode 100644 index 000000000..31d099455 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/package.json @@ -0,0 +1,4 @@ +{ + "name": "test-plugin-import-raw-css-serve-ssr", + "type": "module" +} \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/api/fragment.js b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/api/fragment.js new file mode 100644 index 000000000..af4ced829 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/api/fragment.js @@ -0,0 +1,28 @@ +import { renderFromHTML } from 'wc-compiler'; +import { getProducts } from '../services/products.js'; + +export async function handler() { + const products = await getProducts(); + const { html } = await renderFromHTML(` + ${ + products.map((product) => { + const { name, thumbnail } = product; + + return ` + + `; + }).join('') + } + `, [ + new URL('../components/card.js', import.meta.url) + ]); + + return new Response(html, { + headers: new Headers({ + 'Content-Type': 'text/html' + }) + }); +} \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/components/card.css b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/components/card.css new file mode 100644 index 000000000..db18c7c4a --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/components/card.css @@ -0,0 +1,44 @@ +div { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + border: 1px solid #818181; + width: fit-content; + border-radius: 10px; + padding: 2rem 1rem; + height: 680px; + justify-content: space-between; + background-color: #fff; + overflow-x: hidden; +} + +button { + background: var(--color-accent); + color: var(--color-white); + padding: 1rem 2rem; + border: 0; + font-size: 1rem; + border-radius: 5px; + cursor: pointer; +} + +img { + max-width: 500px; + min-width: 500px; + width: 100%; +} + +h3 { + font-size: 1.85rem; +} + +@media(max-width: 768px) { + img { + max-width: 300px; + min-width: 300px; + } + div { + height: 500px; + } +} \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/components/card.js b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/components/card.js new file mode 100644 index 000000000..5ccf5dee5 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/components/card.js @@ -0,0 +1,31 @@ +import styles from './card.css?type=raw'; + +export default class Card extends HTMLElement { + + selectItem() { + alert(`selected item is => ${this.getAttribute('title')}!`); + } + + connectedCallback() { + if (!this.shadowRoot) { + const thumbnail = this.getAttribute('thumbnail'); + const title = this.getAttribute('title'); + const template = document.createElement('template'); + + template.innerHTML = ` + +
+

${title}

+ ${title} + +
+ `; + this.attachShadow({ mode: 'open' }); + this.shadowRoot.appendChild(template.content.cloneNode(true)); + } + } +} + +customElements.define('app-card', Card); \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/pages/products.js b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/pages/products.js new file mode 100644 index 000000000..09ff462c8 --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/pages/products.js @@ -0,0 +1,31 @@ +import '../components/card.js'; +import { getProducts } from '../services/products.js'; +import styles from '../styles/some.css?type=raw'; + +export default class ProductsPage extends HTMLElement { + async connectedCallback() { + const products = await getProducts(); + const html = products.map(product => { + const { name, thumbnail } = product; + + return ` + + + `; + }).join(''); + + this.innerHTML = ` +

SSR Page (w/ WCC)

+

List of Products: ${products.length}

+ +
+ ${html} +
+ `; + } +} \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/services/products.js b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/services/products.js new file mode 100644 index 000000000..96c999dac --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/services/products.js @@ -0,0 +1,11 @@ +async function getProducts() { + return [{ + name: 'iPhone 12', + thumbnail: 'iphone-12.png' + }, { + name: 'Samsung Galaxy', + thumbnail: 'samsung-galaxy.png' + }]; +} + +export { getProducts }; \ No newline at end of file diff --git a/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/styles/some.css b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/styles/some.css new file mode 100644 index 000000000..9054080ff --- /dev/null +++ b/packages/plugin-import-raw/test/cases/loaders-serve.ssr/src/styles/some.css @@ -0,0 +1,3 @@ +h1 { + color: red; +} \ No newline at end of file diff --git a/packages/plugin-postcss/README.md b/packages/plugin-postcss/README.md index df3ff3079..739cc2c05 100644 --- a/packages/plugin-postcss/README.md +++ b/packages/plugin-postcss/README.md @@ -32,8 +32,6 @@ export default { } ``` -> 👉 _If you are using this along with [**plugin-import-css**](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-css), make sure **plugin-postcss** comes first. All non standard transformations need to come last._ - Optionally, to use your own PostCSS configuration, you'll need to create _two (2)_ config files in the root of your project, by which you can provide your own custom plugins / settings that you've installed. - _postcss.config.js_ - _postcss.config.mjs_ diff --git a/packages/plugin-postcss/src/index.js b/packages/plugin-postcss/src/index.js index ab4280257..305bb21f4 100644 --- a/packages/plugin-postcss/src/index.js +++ b/packages/plugin-postcss/src/index.js @@ -33,11 +33,13 @@ class PostCssResource extends ResourceInterface { this.contentType = ['text/css']; } - async shouldIntercept(url) { - return url.protocol === 'file:' && url.pathname.split('.').pop() === this.extensions[0]; + async shouldPreIntercept(url, request, response) { + return url.protocol === 'file:' + && url.pathname.split('.').pop() === this.extensions[0] + && (request?.headers?.get('Content-Type')?.includes('text/css') || response?.headers?.get('Content-Type')?.includes('text/css')); } - async intercept(url, request, response) { + async preIntercept(url, request, response) { const config = await getConfig(this.compilation, this.options.extendConfig); const plugins = config.plugins || []; const body = await response.text(); diff --git a/packages/plugin-renderer-lit/README.md b/packages/plugin-renderer-lit/README.md index c5f867b51..8cb8e1ba6 100644 --- a/packages/plugin-renderer-lit/README.md +++ b/packages/plugin-renderer-lit/README.md @@ -38,8 +38,9 @@ yarn add @greenwood/plugin-renderer-lit --dev 1. Please familiarize yourself with some of the [caveats](https://lit.dev/docs/ssr/overview/#library-status) called out in the Lit docs, like: - Lit SSR [**only** renders into declarative shadow roots](https://github.com/lit/lit/issues/3080#issuecomment-1165158794), so you will have to keep browser support and polyfill usage in mind. - At this time, `LitElement` does not support `async` work. You can follow along with this issue [in the Lit repo](https://github.com/lit/lit/issues/2469). -1. Lit only supports templates on the server side for HTML only generated content, thus Greenwood's `getBody` API must be used. We would love for [server only components](https://github.com/lit/lit/issues/2469#issuecomment-1759583861) to be a thing though! -1. Full hydration support is not available yet. See [this Greenwood issue](https://github.com/ProjectEvergreen/greenwood/issues/880) to follow along when it will land +1. Lit only supports templates on the server side for HTML generated content, thus Greenwood's `getBody` API must be used. We would love for [server only components](https://github.com/lit/lit/issues/2469#issuecomment-1759583861) to be a thing though! +1. Lit does not support [`CSSStyleSheet` (aka CSS Modules) in their SSR DOM shim](https://github.com/lit/lit/issues/2631#issuecomment-1065400805). +1. Full hydration support is not available yet. See [this Greenwood issue](https://github.com/ProjectEvergreen/greenwood/issues/880) to follow along with when it will land. > See [this repo](https://github.com/thescientist13/greenwood-lit-ssr) for a full demo of isomorphic Lit SSR with SSR pages and API routes deployed to Vercel serverless functions. diff --git a/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/build.prerender.getting-started.spec.js b/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/build.prerender.getting-started.spec.js index 34ac418e6..4c5455bcb 100644 --- a/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/build.prerender.getting-started.spec.js +++ b/packages/plugin-renderer-lit/test/cases/build.prerender.getting-started/build.prerender.getting-started.spec.js @@ -10,7 +10,7 @@ * greenwood build * * User Config - * import { greenwoodPluginIncludeHTML } from '@greenwood/plugin-include-html'; + * import { greenwoodPluginRendererLit } from '@greenwood/plugin-renderer-lit'; * * { * plugins: [{ @@ -166,7 +166,7 @@ describe('Build Greenwood With Custom Lit Renderer for SSG prerendering: ', func dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html')); }); - it('should have expected footer

tag content in the ', function() { + it('should have no script tags in the ', function() { const scripTags = dom.window.document.querySelectorAll('body script'); expect(scripTags.length).to.be.equal(0); @@ -182,7 +182,7 @@ describe('Build Greenwood With Custom Lit Renderer for SSG prerendering: ', func body = dom.window.document.querySelector('body'); }); - it('should have expected footer

tag content in the ', function() { + it('should have expected

tag content in the
', function() { const html = body.innerHTML.trim(); expect(html).to.contain('
'); diff --git a/packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/greenwood.config.js b/packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/greenwood.config.js new file mode 100644 index 000000000..4b8dff4c9 --- /dev/null +++ b/packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/greenwood.config.js @@ -0,0 +1,9 @@ +import { greenwoodPluginRendererLit } from '../../../src/index.js'; + +export default { + plugins: [ + greenwoodPluginRendererLit({ + prerender: true + }) + ] +}; \ No newline at end of file diff --git a/packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/loaders-build.prerender.import-attributes.spec.js b/packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/loaders-build.prerender.import-attributes.spec.js new file mode 100644 index 000000000..9e06e55ae --- /dev/null +++ b/packages/plugin-renderer-lit/test/cases/loaders-build.prerender.import-attributes/loaders-build.prerender.import-attributes.spec.js @@ -0,0 +1,193 @@ +/* + * Use Case + * Run Greenwood build command with a static site and only prerendering the content (no JS!) and using import attributes + * + * User Result + * Should generate a bare bones Greenwood build with correctly templated out HTML from a LitElement. + * + * User Command + * greenwood build + * + * User Config + * import { greenwoodPluginRendererLit } from '@greenwood/plugin-renderer-lit'; + * + * { + * plugins: [{ + * greenwoodPluginRendererLit({ + * prerender: true + * }) + * }] + * } + * + * User Workspace + * src/ + * components/ + * header/ + * header.js + * header.css + * nav.json + * pages/ + * index.html + */ +import chai from 'chai'; +import { JSDOM } from 'jsdom'; +import path from 'path'; +import { runSmokeTest } from '../../../../../test/smoke-test.js'; +import { getDependencyFiles, getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Build Greenwood With Custom Lit Renderer for SSG prerendering: ', function() { + const LABEL = 'For SSG prerendering of Getting Started example'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(false, true); + }); + + describe(LABEL, function() { + + before(async function() { + const lit = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/*.js`, + `${outputPath}/node_modules/lit/` + ); + const litDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/decorators/*.js`, + `${outputPath}/node_modules/lit/decorators/` + ); + const litDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/directives/*.js`, + `${outputPath}/node_modules/lit/directives/` + ); + const litPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/package.json`, + `${outputPath}/node_modules/lit/` + ); + const litElement = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/*.js`, + `${outputPath}/node_modules/lit-element/` + ); + const litElementPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/package.json`, + `${outputPath}/node_modules/lit-element/` + ); + const litElementDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/decorators/*.js`, + `${outputPath}/node_modules/lit-element/decorators/` + ); + const litHtml = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/*.js`, + `${outputPath}/node_modules/lit-html/` + ); + const litHtmlNode = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/node/*.js`, + `${outputPath}/node_modules/lit-html/node/` + ); + const litHtmlNodeDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/node/directives/*.js`, + `${outputPath}/node_modules/lit-html/node/directives/` + ); + const litHtmlPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/package.json`, + `${outputPath}/node_modules/lit-html/` + ); + const litHtmlDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/directives/*.js`, + `${outputPath}/node_modules/lit-html/directives/` + ); + const litReactiveElement = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/` + ); + const litReactiveElementDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/decorators/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/decorators/` + ); + const litReactiveElementPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/package.json`, + `${outputPath}/node_modules/@lit/reactive-element/` + ); + const litReactiveElementNode = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/node/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/node/` + ); + // lit-html/node/directives/unsafe-html.js + const litHtmlSourceMap = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/lit-html.js.map`, + `${outputPath}/node_modules/lit-html/` + ); + const trustedTypes = await getDependencyFiles( + `${process.cwd()}/node_modules/@types/trusted-types/package.json`, + `${outputPath}/node_modules/@types/trusted-types/` + ); + + runner.setup(outputPath, [ + ...getSetupFiles(outputPath), + ...lit, + ...litPackageJson, + ...litDirectives, + ...litDecorators, + ...litElementPackageJson, + ...litElement, + ...litElementDecorators, + ...litHtmlPackageJson, + ...litHtml, + ...litHtmlNode, + ...litHtmlNodeDirectives, + ...litHtmlDirectives, + ...trustedTypes, + ...litReactiveElement, + ...litReactiveElementDecorators, + ...litReactiveElementPackageJson, + ...litReactiveElementNode, + ...litHtmlSourceMap + ]); + runner.runCommand(cliPath, 'build'); + }); + + runSmokeTest(['public', 'index'], LABEL); + + describe(' of the page with data-gwd-opt="static" script tags removed', function() { + let dom; + + before(async function() { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html')); + }); + + it('should have no script tags in the ', function() { + const scripTags = dom.window.document.querySelectorAll('body script'); + + expect(scripTags.length).to.be.equal(0); + }); + }); + + describe('LitElement statically rendered into index.html', function() { + let dom; + + before(async function() { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html')); + }); + + it('should have expected header