-
Notifications
You must be signed in to change notification settings - Fork 439
enable hydration for SSR v2 #5226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a82076d
f381308
342ae8d
de4f777
0ef4402
4923180
c9f4c66
f8c1670
b5d0414
807f4d6
50516ed
3e31a4d
7ba157f
fd5ebda
ec93224
6f39aa5
76e905c
fe96c28
4af38ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -178,6 +178,11 @@ jobs: | |
| - run: DISABLE_STATIC_CONTENT_OPTIMIZATION=1 DISABLE_SYNTHETIC=1 yarn sauce:ci | ||
| - run: NODE_ENV_FOR_TEST=production yarn sauce:ci | ||
| - run: NODE_ENV_FOR_TEST=production DISABLE_SYNTHETIC=1 yarn sauce:ci | ||
| - run: yarn hydration:sauce:ci:engine-server | ||
| - run: ENABLE_SYNTHETIC_SHADOW_IN_HYDRATION=1 yarn hydration:sauce:ci:engine-server | ||
| - run: NODE_ENV_FOR_TEST=production yarn hydration:sauce:ci:engine-server | ||
| - run: DISABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 yarn hydration:sauce:ci:engine-server | ||
| - run: DISABLE_STATIC_CONTENT_OPTIMIZATION=1 yarn hydration:sauce:ci:engine-server | ||
|
Comment on lines
+181
to
+185
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these added to the shortest-running group of tests? |
||
| - run: yarn hydration:sauce:ci | ||
| - run: ENABLE_SYNTHETIC_SHADOW_IN_HYDRATION=1 yarn hydration:sauce:ci | ||
| - run: NODE_ENV_FOR_TEST=production yarn hydration:sauce:ci | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,16 +4,29 @@ | |
| * SPDX-License-Identifier: MIT | ||
| * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT | ||
| */ | ||
| const ENGINE_SERVER = process.env.ENGINE_SERVER; | ||
| const path = require('node:path'); | ||
| const vm = require('node:vm'); | ||
| const fs = require('node:fs/promises'); | ||
| const { format } = require('node:util'); | ||
| const { rollup } = require('rollup'); | ||
| const lwcRollupPlugin = require('@lwc/rollup-plugin'); | ||
| const ssr = require('@lwc/engine-server'); | ||
| const ssr = ENGINE_SERVER ? require('@lwc/engine-server') : require('@lwc/ssr-runtime'); | ||
| const { DISABLE_STATIC_CONTENT_OPTIMIZATION } = require('../shared/options'); | ||
| const Watcher = require('./Watcher'); | ||
|
|
||
| /* | ||
| * These are hydration tests that currently fail in ssr-compiler (V2) | ||
| * They need to be removed from this list and then fixed. There are only 4 | ||
| * so kept here for now. | ||
| */ | ||
| const EXPECTED_V2_FAILURES = [ | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These failures only occur in V2, due to engine differences. I didn't want to solve them in this PR but there are only 4 and they should be quick to resolve so I kept them in this file. Could also import them if someone feels strongly. I can open bugs for them?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can create one WI for all of them, if you think they'll be quick fixes. If they'll take individual effort, multiple WIs is fine. Also, please add corresponding tests to the SSR test suite so we can get easier signal about future breakage without having to run the integration/hydration tests. |
||
| 'mismatches/host-mutation-in-connected-callback/class-mutated-attr-mismatch', | ||
| 'mismatches/host-mutation-in-connected-callback/class', | ||
| 'directives/lwc-dynamic', | ||
| 'inner-outer-html', | ||
| ]; | ||
|
|
||
| const context = { | ||
| LWC: ssr, | ||
| moduleOutput: null, | ||
|
|
@@ -54,13 +67,12 @@ async function exists(path) { | |
| } | ||
| } | ||
|
|
||
| let cache; | ||
|
|
||
| async function getCompiledModule(dirName) { | ||
| async function getCompiledModule(dirName, compileForSSR) { | ||
| const bundle = await rollup({ | ||
| input: path.join(dirName, 'x', COMPONENT_UNDER_TEST, `${COMPONENT_UNDER_TEST}.js`), | ||
| plugins: [ | ||
| lwcRollupPlugin({ | ||
| targetSSR: !!compileForSSR, | ||
| modules: [ | ||
| { | ||
| dir: dirName, | ||
|
|
@@ -74,9 +86,8 @@ async function getCompiledModule(dirName) { | |
| enableStaticContentOptimization: !DISABLE_STATIC_CONTENT_OPTIMIZATION, | ||
| }), | ||
| ], | ||
| cache, | ||
|
|
||
| external: ['lwc', 'test-utils', '@test/loader'], // @todo: add ssr modules for test-utils and @test/loader | ||
| external: ['lwc', '@lwc/ssr-runtime', 'test-utils', '@test/loader'], // @todo: add ssr modules for test-utils and @test/loader | ||
divmain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| onwarn(warning, warn) { | ||
| // Ignore warnings from our own Rollup plugin | ||
|
|
@@ -87,13 +98,13 @@ async function getCompiledModule(dirName) { | |
| }); | ||
|
|
||
| const { watchFiles } = bundle; | ||
| cache = bundle.cache; | ||
|
|
||
| const { output } = await bundle.generate({ | ||
| format: 'iife', | ||
| name: 'Main', | ||
| globals: { | ||
| lwc: 'LWC', | ||
| '@lwc/ssr-runtime': 'LWC', | ||
| 'test-utils': 'TestUtils', | ||
| }, | ||
| }); | ||
|
|
@@ -131,25 +142,30 @@ function throwOnUnexpectedConsoleCalls(runnable) { | |
| } | ||
|
|
||
| /** | ||
| * @param {string} moduleCode | ||
| * @param {string} testConfig | ||
| * @param {string} filename | ||
| * This is the function that takes SSR bundle code and test config, constructs a script that will | ||
| * run in a separate JS runtime environment with its own global scope. The `context` object | ||
| * (defined at the top of this file) is passed in as the global scope for that script. The script | ||
| * runs, utilizing the `LWC` object that we've attached to the global scope, it sets a | ||
| * new value (the rendered markup) to the globalThis.moduleOutput, which correspond to | ||
| * context.moduleOutput in the hydration-test.js module scope. | ||
| * | ||
| * So, script runs, generates markup, & we get that markup out and return it to Karma for use | ||
| * in client-side tests. | ||
| */ | ||
| function getSsrCode(moduleCode, testConfig, filename) { | ||
| async function getSsrCode(moduleCode, testConfig, filename) { | ||
| const script = new vm.Script( | ||
| ` | ||
| ${testConfig}; | ||
| config = config || {}; | ||
| ${moduleCode}; | ||
| moduleOutput = LWC.renderComponent('x-${COMPONENT_UNDER_TEST}-${guid++}', Main, config.props || {});`, | ||
| moduleOutput = LWC.renderComponent('x-${COMPONENT_UNDER_TEST}-${guid++}', Main, config.props || {}, 'sync');`, | ||
| { filename } | ||
| ); | ||
|
|
||
| throwOnUnexpectedConsoleCalls(() => { | ||
| vm.createContext(context); | ||
| script.runInContext(context); | ||
| }); | ||
|
|
||
| return context.moduleOutput; | ||
| } | ||
|
|
||
|
|
@@ -160,7 +176,6 @@ async function getTestModuleCode(input) { | |
| }); | ||
|
|
||
| const { watchFiles } = bundle; | ||
| cache = bundle.cache; | ||
|
|
||
| const { output } = await bundle.generate({ | ||
| format: 'iife', | ||
|
|
@@ -194,33 +209,66 @@ function createHCONFIG2JSPreprocessor(config, logger, emitter) { | |
| return async (_content, file, done) => { | ||
| const filePath = file.path; | ||
| const suiteDir = path.dirname(filePath); | ||
|
|
||
| // Wrap all the tests into a describe block with the file stricture name | ||
| const describeTitle = path.relative(basePath, suiteDir).split(path.sep).join(' '); | ||
|
|
||
| try { | ||
| const { code: testCode, watchFiles: testWatchFiles } = | ||
| await getTestModuleCode(filePath); | ||
| const { code: componentDef, watchFiles: componentWatchFiles } = | ||
| await getCompiledModule(suiteDir); | ||
|
|
||
| // You can add an `.only` file alongside an `index.spec.js` file to make it `fdescribe()` | ||
| const onlyFileExists = await existsUp(suiteDir, '.only'); | ||
| // If V2 failure is expected, skip it. | ||
| const v2FailureExpected = | ||
| !ENGINE_SERVER && EXPECTED_V2_FAILURES.some((dir) => suiteDir.includes(dir)); | ||
|
|
||
| let describe = onlyFileExists ? 'fdescribe' : 'describe'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like this variable name. |
||
|
|
||
| const { code: componentDefCSR, watchFiles: componentWatchFilesCSR } = | ||
| await getCompiledModule(suiteDir, false); | ||
|
|
||
| let ssrOutput; | ||
| /* eslint-disable vitest/no-conditional-tests */ | ||
| if (ENGINE_SERVER) { | ||
divmain marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // engine-server uses the same def as the client | ||
| watcher.watchSuite(filePath, testWatchFiles.concat(componentWatchFilesCSR)); | ||
| ssrOutput = await getSsrCode( | ||
| componentDefCSR, | ||
| testCode, | ||
| path.join(suiteDir, 'ssr.js') | ||
| ); | ||
| } else if (!v2FailureExpected) { | ||
| // ssr-compiler has it's own def | ||
| const { code: componentDefSSR, watchFiles: componentWatchFilesSSR } = | ||
| await getCompiledModule(suiteDir, true); | ||
| watcher.watchSuite( | ||
| filePath, | ||
| testWatchFiles.concat(componentWatchFilesCSR).concat(componentWatchFilesSSR) | ||
| ); | ||
| ssrOutput = await getSsrCode( | ||
| componentDefSSR.replace(`process.env.NODE_ENV === 'test-karma-lwc'`, 'true'), | ||
| testCode, | ||
| path.join(suiteDir, 'ssr.js') | ||
| ); | ||
| } else { | ||
| console.log(`Expected failure for ${suiteDir}, skipping`); | ||
| describe = 'xdescribe'; | ||
| } | ||
| /* eslint-enable vitest/no-conditional-tests */ | ||
|
|
||
| const ssrOutput = getSsrCode(componentDef, testCode, path.join(suiteDir, 'ssr.js')); | ||
|
|
||
| watcher.watchSuite(filePath, testWatchFiles.concat(componentWatchFiles)); | ||
| const newContent = format( | ||
| TEMPLATE, | ||
| JSON.stringify(ssrOutput), | ||
| componentDef, | ||
| componentDefCSR, | ||
| testCode, | ||
| onlyFileExists ? 'fdescribe' : 'describe', | ||
| describe, | ||
| JSON.stringify(describeTitle) | ||
| ); | ||
| done(null, newContent); | ||
| } catch (error) { | ||
| const location = path.relative(basePath, filePath); | ||
| log.error('Error processing “%s”\n\n%s\n', location, error.stack || error.message); | ||
|
|
||
| done(error, null); | ||
| } | ||
| }; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.