From afe1d11a5b822b04453c05bc9702f99f588e903a Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 10 Apr 2024 21:00:37 +0100 Subject: [PATCH] feat: hot module reloading support for Svelte 5 (#11106) * feat: hot module reloading support for Svelte 5 * fix lockfile * tweaks * types * lint * lint * tweaks * add hmr flag * tweak * tweaks * move HMR logic into its own module * simplify * tidy up types * fix test * lint * need some indirection here or references break * prevent transitions during HMR update --------- Co-authored-by: Rich Harris --- .changeset/olive-moons-act.md | 5 +++ .../3-transform/client/transform-client.js | 28 +++++++++--- .../svelte/src/compiler/utils/builders.js | 14 ++++++ .../svelte/src/internal/client/dev/hmr.js | 44 +++++++++++++++++++ packages/svelte/src/internal/client/index.js | 1 + packages/svelte/src/internal/client/render.js | 2 +- .../parser-legacy/samples/css/output.json | 4 +- .../whitespace-after-style-tag/output.json | 4 +- .../samples/css-nth-syntax/output.json | 4 +- .../samples/css-pseudo-classes/output.json | 4 +- .../semicolon-inside-quotes/output.json | 4 +- .../_expected/client/index.svelte.js | 6 ++- .../_expected/client/index.svelte.js | 6 ++- .../_expected/client/index.svelte.js | 6 ++- .../_expected/client/main.svelte.js | 6 ++- .../_expected/client/index.svelte.js | 6 ++- .../_expected/client/index.svelte.js | 6 ++- .../_expected/client/index.svelte.js | 6 ++- .../_expected/client/index.svelte.js | 4 +- .../_expected/client/index.svelte.js | 6 ++- playgrounds/demo/server.js | 33 ++++---------- playgrounds/demo/src/entry-client.ts | 10 +++-- .../src/lib/Output/Output.svelte | 2 +- 23 files changed, 147 insertions(+), 64 deletions(-) create mode 100644 .changeset/olive-moons-act.md create mode 100644 packages/svelte/src/internal/client/dev/hmr.js diff --git a/.changeset/olive-moons-act.md b/.changeset/olive-moons-act.md new file mode 100644 index 0000000000..dcfe7c3e7c --- /dev/null +++ b/.changeset/olive-moons-act.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: hot module reloading support for Svelte 5 diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index f77766a8e7..2348307b51 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -415,15 +415,31 @@ export function client_component(source, analysis, options) { const body = [ ...state.hoisted, ...module.body, - b.export_default( - b.function_declaration( - b.id(analysis.name), - [b.id('$$anchor'), b.id('$$props')], - component_block - ) + b.function_declaration( + b.id(analysis.name), + [b.id('$$anchor'), b.id('$$props')], + component_block ) ]; + if (options.hmr) { + body.push( + b.export_default( + b.conditional( + b.import_meta_hot(), + b.call('$.hmr', b.member(b.import_meta_hot(), b.id('data')), b.id(analysis.name)), + b.id(analysis.name) + ) + ), + b.if( + b.import_meta_hot(), + b.stmt(b.call('import.meta.hot.acceptExports', b.literal('default'))) + ) + ); + } else { + body.push(b.export_default(b.id(analysis.name))); + } + if (options.dev) { if (options.filename) { let filename = options.filename; diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js index a5776a46de..1b7da19d24 100644 --- a/packages/svelte/src/compiler/utils/builders.js +++ b/packages/svelte/src/compiler/utils/builders.js @@ -600,6 +600,20 @@ export function throw_error(str) { }; } +/** + * @return {import('estree').MemberExpression} + */ +export function import_meta_hot() { + return member( + { + type: 'MetaProperty', + meta: id('import'), + property: id('meta') + }, + id('hot') + ); +} + export { await_builder as await, let_builder as let, diff --git a/packages/svelte/src/internal/client/dev/hmr.js b/packages/svelte/src/internal/client/dev/hmr.js new file mode 100644 index 0000000000..9c42956976 --- /dev/null +++ b/packages/svelte/src/internal/client/dev/hmr.js @@ -0,0 +1,44 @@ +import { block, branch, destroy_effect } from '../reactivity/effects.js'; +import { set, source } from '../reactivity/sources.js'; +import { set_should_intro } from '../render.js'; +import { get } from '../runtime.js'; + +/** + * @template {(anchor: Comment, props: any) => any} Component + * @param {{ source: import("#client").Source; wrapper: Component; }} data + * @param {Component} component + */ +export function hmr(data, component) { + if (data.source) { + set(data.source, component); + } else { + data.source = source(component); + } + + return (data.wrapper ??= /** @type {Component} */ ( + (anchor, props) => { + let instance = {}; + + /** @type {import("#client").Effect} */ + let effect; + + block(() => { + const component = get(data.source); + + if (effect) { + // @ts-ignore + for (var k in instance) delete instance[k]; + destroy_effect(effect); + } + + effect = branch(() => { + set_should_intro(false); + Object.assign(instance, component(anchor, props)); + set_should_intro(true); + }); + }); + + return instance; + } + )); +} diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index af3e4ebd2e..86c8fbc7da 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -1,3 +1,4 @@ +export { hmr } from './dev/hmr.js'; export { add_owner, mark_module_start, mark_module_end } from './dev/ownership.js'; export { await_block as await } from './dom/blocks/await.js'; export { if_block as if } from './dom/blocks/if.js'; diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 833070925e..f9c4dbf1a7 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -7,7 +7,7 @@ import { init_operations } from './dom/operations.js'; import { HYDRATION_START, PassiveDelegatedEvents } from '../../constants.js'; -import { flush_sync, push, pop, current_component_context, untrack } from './runtime.js'; +import { flush_sync, push, pop, current_component_context } from './runtime.js'; import { effect_root, branch } from './reactivity/effects.js'; import { hydrate_anchor, diff --git a/packages/svelte/tests/parser-legacy/samples/css/output.json b/packages/svelte/tests/parser-legacy/samples/css/output.json index 3667dbc0b8..b3ecd7b671 100644 --- a/packages/svelte/tests/parser-legacy/samples/css/output.json +++ b/packages/svelte/tests/parser-legacy/samples/css/output.json @@ -78,8 +78,8 @@ "content": { "start": 23, "end": 48, - "comment": null, - "styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n" + "styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n", + "comment": null } } } diff --git a/packages/svelte/tests/parser-legacy/samples/whitespace-after-style-tag/output.json b/packages/svelte/tests/parser-legacy/samples/whitespace-after-style-tag/output.json index e109595656..2cadbc672f 100644 --- a/packages/svelte/tests/parser-legacy/samples/whitespace-after-style-tag/output.json +++ b/packages/svelte/tests/parser-legacy/samples/whitespace-after-style-tag/output.json @@ -78,8 +78,8 @@ "content": { "start": 23, "end": 48, - "comment": null, - "styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n" + "styles": "\n\tdiv {\n\t\tcolor: red;\n\t}\n", + "comment": null } } } diff --git a/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json index cd92514082..e8f0ca2a4b 100644 --- a/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json +++ b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json @@ -1074,8 +1074,8 @@ "content": { "start": 7, "end": 798, - "comment": null, - "styles": "\n /* test that all these are parsed correctly */\n\th1:nth-of-type(2n+1){\n background: red;\n }\n h1:nth-child(-n + 3 of li.important) {\n background: red;\n }\n h1:nth-child(1) {\n background: red;\n }\n h1:nth-child(p) {\n background: red;\n }\n h1:nth-child(n+7) {\n background: red;\n }\n h1:nth-child(even) {\n background: red;\n }\n h1:nth-child(odd) {\n background: red;\n }\n h1:nth-child(\n n\n ) {\n background: red;\n }\n h1:global(nav) {\n background: red;\n }\n\t\th1:nth-of-type(10n+1){\n background: red;\n }\n\t\th1:nth-of-type(-2n+3){\n background: red;\n }\n\t\th1:nth-of-type(+12){\n background: red;\n }\n\t\th1:nth-of-type(+3n){\n background: red;\n }\n" + "styles": "\n /* test that all these are parsed correctly */\n\th1:nth-of-type(2n+1){\n background: red;\n }\n h1:nth-child(-n + 3 of li.important) {\n background: red;\n }\n h1:nth-child(1) {\n background: red;\n }\n h1:nth-child(p) {\n background: red;\n }\n h1:nth-child(n+7) {\n background: red;\n }\n h1:nth-child(even) {\n background: red;\n }\n h1:nth-child(odd) {\n background: red;\n }\n h1:nth-child(\n n\n ) {\n background: red;\n }\n h1:global(nav) {\n background: red;\n }\n\t\th1:nth-of-type(10n+1){\n background: red;\n }\n\t\th1:nth-of-type(-2n+3){\n background: red;\n }\n\t\th1:nth-of-type(+12){\n background: red;\n }\n\t\th1:nth-of-type(+3n){\n background: red;\n }\n", + "comment": null } }, "js": [], diff --git a/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json index 9c9f5335c2..ed7bd7c8e0 100644 --- a/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json +++ b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json @@ -393,8 +393,8 @@ "content": { "start": 7, "end": 378, - "comment": null, - "styles": "\n /* test that all these are parsed correctly */\n\t::view-transition-old(x-y) {\n\t\tcolor: red;\n }\n\t:global(::view-transition-old(x-y)) {\n\t\tcolor: red;\n }\n\t::highlight(rainbow-color-1) {\n\t\tcolor: red;\n\t}\n\tcustom-element::part(foo) {\n\t\tcolor: red;\n\t}\n\t::slotted(.content) {\n\t\tcolor: red;\n\t}\n\t:is( /*button*/\n\t\tbutton, /*p after h1*/\n\t\th1 + p\n\t\t){\n\t\tcolor: red;\n\t}\n" + "styles": "\n /* test that all these are parsed correctly */\n\t::view-transition-old(x-y) {\n\t\tcolor: red;\n }\n\t:global(::view-transition-old(x-y)) {\n\t\tcolor: red;\n }\n\t::highlight(rainbow-color-1) {\n\t\tcolor: red;\n\t}\n\tcustom-element::part(foo) {\n\t\tcolor: red;\n\t}\n\t::slotted(.content) {\n\t\tcolor: red;\n\t}\n\t:is( /*button*/\n\t\tbutton, /*p after h1*/\n\t\th1 + p\n\t\t){\n\t\tcolor: red;\n\t}\n", + "comment": null } }, "js": [], diff --git a/packages/svelte/tests/parser-modern/samples/semicolon-inside-quotes/output.json b/packages/svelte/tests/parser-modern/samples/semicolon-inside-quotes/output.json index aee039361c..b900bfc0b3 100644 --- a/packages/svelte/tests/parser-modern/samples/semicolon-inside-quotes/output.json +++ b/packages/svelte/tests/parser-modern/samples/semicolon-inside-quotes/output.json @@ -71,8 +71,8 @@ "content": { "start": 43, "end": 197, - "comment": null, - "styles": "\n\t@import url(\"https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap\");\n\th1 {\n\t\tfont-weight: bold;\n\t\tbackground: url(\"whatever\");\n\t}\n" + "styles": "\n\t@import url(\"https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap\");\n\th1 {\n\t\tfont-weight: bold;\n\t\tbackground: url(\"whatever\");\n\t}\n", + "comment": null } }, "js": [], diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js index e9bfe03063..ba25326774 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js @@ -7,7 +7,7 @@ import TextInput from './Child.svelte'; var root_1 = $.template(`Something`, 1); var root = $.template(` `, 1); -export default function Bind_component_snippet($$anchor, $$props) { +function Bind_component_snippet($$anchor, $$props) { $.push($$props, true); let value = $.source(''); @@ -36,4 +36,6 @@ export default function Bind_component_snippet($$anchor, $$props) { $.render_effect(() => $.set_text(text, ` value: ${$.stringify($.get(value))}`)); $.append($$anchor, fragment_1); $.pop(); -} \ No newline at end of file +} + +export default Bind_component_snippet; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js index b14eaa71a9..40149aacef 100644 --- a/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js @@ -3,7 +3,7 @@ import "svelte/internal/disclose-version"; import * as $ from "svelte/internal/client"; -export default function Bind_this($$anchor, $$props) { +function Bind_this($$anchor, $$props) { $.push($$props, false); $.init(); @@ -13,4 +13,6 @@ export default function Bind_this($$anchor, $$props) { $.bind_this(Foo(node, {}), ($$value) => foo = $$value, () => foo); $.append($$anchor, fragment); $.pop(); -} \ No newline at end of file +} + +export default Bind_this; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js index d2c1c9c540..2b0a847d85 100644 --- a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js @@ -3,7 +3,7 @@ import "svelte/internal/disclose-version"; import * as $ from "svelte/internal/client"; -export default function Class_state_field_constructor_assignment($$anchor, $$props) { +function Class_state_field_constructor_assignment($$anchor, $$props) { $.push($$props, true); class Foo { @@ -26,4 +26,6 @@ export default function Class_state_field_constructor_assignment($$anchor, $$pro } $.pop(); -} \ No newline at end of file +} + +export default Class_state_field_constructor_assignment; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js index 9d6dff7a3b..d3d0ece855 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js @@ -5,7 +5,7 @@ import * as $ from "svelte/internal/client"; var root = $.template(`
`, 3); -export default function Main($$anchor, $$props) { +function Main($$anchor, $$props) { $.push($$props, true); // needs to be a snapshot test because jsdom does auto-correct the attribute casing @@ -35,4 +35,6 @@ export default function Main($$anchor, $$props) { $.append($$anchor, fragment); $.pop(); -} \ No newline at end of file +} + +export default Main; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js index fee82a15b9..1df7634eef 100644 --- a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js @@ -3,7 +3,7 @@ import "svelte/internal/disclose-version"; import * as $ from "svelte/internal/client"; -export default function Each_string_template($$anchor, $$props) { +function Each_string_template($$anchor, $$props) { $.push($$props, false); $.init(); @@ -19,4 +19,6 @@ export default function Each_string_template($$anchor, $$props) { $.append($$anchor, fragment); $.pop(); -} \ No newline at end of file +} + +export default Each_string_template; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js index f5f31c253d..30de978c7c 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js @@ -3,7 +3,7 @@ import "svelte/internal/disclose-version"; import * as $ from "svelte/internal/client"; -export default function Function_prop_no_getter($$anchor, $$props) { +function Function_prop_no_getter($$anchor, $$props) { $.push($$props, true); let count = $.source(0); @@ -30,4 +30,6 @@ export default function Function_prop_no_getter($$anchor, $$props) { $.append($$anchor, fragment); $.pop(); -} \ No newline at end of file +} + +export default Function_prop_no_getter; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js index 4f0aadff19..c973f87e21 100644 --- a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js @@ -5,7 +5,7 @@ import * as $ from "svelte/internal/client"; var root = $.template(`

hello world

`); -export default function Hello_world($$anchor, $$props) { +function Hello_world($$anchor, $$props) { $.push($$props, false); $.init(); @@ -13,4 +13,6 @@ export default function Hello_world($$anchor, $$props) { $.append($$anchor, h1); $.pop(); -} \ No newline at end of file +} + +export default Hello_world; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js index 9d5954ade2..162ace8501 100644 --- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js @@ -12,7 +12,7 @@ function reset(_, str, tpl) { var root = $.template(` `, 1); -export default function State_proxy_literal($$anchor, $$props) { +function State_proxy_literal($$anchor, $$props) { $.push($$props, true); let str = $.source(''); @@ -35,4 +35,6 @@ export default function State_proxy_literal($$anchor, $$props) { $.pop(); } +export default State_proxy_literal; + $.delegate(["click"]); \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js index e9db44cd7a..0396cbe092 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js @@ -3,7 +3,7 @@ import "svelte/internal/disclose-version"; import * as $ from "svelte/internal/client"; -export default function Svelte_element($$anchor, $$props) { +function Svelte_element($$anchor, $$props) { $.push($$props, true); let tag = $.prop($$props, "tag", 3, 'hr'); @@ -13,4 +13,6 @@ export default function Svelte_element($$anchor, $$props) { $.element(node, tag, false); $.append($$anchor, fragment); $.pop(); -} \ No newline at end of file +} + +export default Svelte_element; \ No newline at end of file diff --git a/playgrounds/demo/server.js b/playgrounds/demo/server.js index ebac035953..5de7e4c969 100644 --- a/playgrounds/demo/server.js +++ b/playgrounds/demo/server.js @@ -2,9 +2,9 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import express from 'express'; -import { createServer as createViteServer, build } from 'vite'; +import { createServer as createViteServer } from 'vite'; -const PORT = process.env.PORT || '3000'; +const PORT = process.env.PORT || '5173'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -26,30 +26,13 @@ async function createServer() { return; } - // Uncomment the line below to enable optimizer. - // process.env.SVELTE_ENV = 'hydrate'; + const template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8'); + const transformed_template = await vite.transformIndexHtml(req.originalUrl, template); + const { html: appHtml, head: headHtml } = await vite.ssrLoadModule('./src/entry-server.ts'); - await build({ - root: path.resolve(__dirname, './'), - build: { - minify: false, - rollupOptions: { - output: { - manualChunks(id) { - if (id.includes('svelte/src')) { - return 'vendor'; - } - } - } - } - } - }); - - const template = fs.readFileSync(path.resolve(__dirname, 'dist', 'index.html'), 'utf-8'); - - const { html: appHtml, head: headHtml } = await vite.ssrLoadModule('/src/entry-server.ts'); - - const html = template.replace(``, appHtml).replace(``, headHtml); + const html = transformed_template + .replace(``, appHtml) + .replace(``, headHtml); res.status(200).set({ 'Content-Type': 'text/html' }).end(html); }); diff --git a/playgrounds/demo/src/entry-client.ts b/playgrounds/demo/src/entry-client.ts index 1cb573735c..8ec831e425 100644 --- a/playgrounds/demo/src/entry-client.ts +++ b/playgrounds/demo/src/entry-client.ts @@ -1,8 +1,10 @@ -// @ts-ignore -import { mount, unmount } from 'svelte'; -// @ts-ignore you need to create this file +import { mount, hydrate, unmount } from 'svelte'; import App from './App.svelte'; -const component = mount(App, { + +const root = document.getElementById('root')!; +const render = root.firstChild?.nextSibling ? hydrate : mount; + +const component = render(App, { target: document.getElementById('root')! }); // @ts-ignore diff --git a/sites/svelte-5-preview/src/lib/Output/Output.svelte b/sites/svelte-5-preview/src/lib/Output/Output.svelte index eebb8a23ad..b23c2af48a 100644 --- a/sites/svelte-5-preview/src/lib/Output/Output.svelte +++ b/sites/svelte-5-preview/src/lib/Output/Output.svelte @@ -34,7 +34,7 @@ /** @type {import('../workers/workers').CompileMessageData | null} */ export let compiled; - $: if (selected) { + $: if (selected && js_editor && css_editor) { if (selected.type === 'json') { js_editor.set({ code: `/* Select a component to see its compiled code */`, lang: 'js' }); css_editor.set({ code: `/* Select a component to see its compiled code */`, lang: 'css' });