svelte/register compatible with .mjs files including svelte components

pull/5242/head
Amir Nouri 5 years ago
parent f4f16da455
commit d4bb5f4a7a

@ -1,20 +1,32 @@
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
const { compile } = require('./compiler.js'); const { compile } = require("./compiler.js");
const extensions = ['.svelte', '.html']; const extensions = [".svelte", ".html"];
let es6Compiler;
let compileOptions = {}; let compileOptions = {};
function compileES6(code) {
try {
return es6Compiler(code);
} catch (error) {
console.log(`error compiling esm file ${error}`);
}
}
function capitalise(name) { function capitalise(name) {
return name[0].toUpperCase() + name.slice(1); return name[0].toUpperCase() + name.slice(1);
} }
function register(options = {}) { function register(options = {}, es6_compiler) {
if (options.extensions) { if (options.extensions) {
extensions.forEach(deregisterExtension); extensions.forEach(deregisterExtension);
options.extensions.forEach(registerExtension); options.extensions.forEach(registerExtension);
} }
// To compile mjs extensions
if (es6_compiler) es6Compiler = es6_compiler;
compileOptions = Object.assign({}, options); compileOptions = Object.assign({}, options);
delete compileOptions.extensions; delete compileOptions.extensions;
} }
@ -24,33 +36,51 @@ function deregisterExtension(extension) {
} }
function registerExtension(extension) { function registerExtension(extension) {
require.extensions[extension] = function(module, filename) { require.extensions[extension] = function (module, filename) {
const name = path.parse(filename).name const name = path
.replace(/^\d/, '_$&') .parse(filename)
.replace(/[^a-zA-Z0-9_$]/g, ''); .name.replace(/^\d/, "_$&")
.replace(/[^a-zA-Z0-9_$]/g, "");
const options = Object.assign({}, compileOptions, { const options = Object.assign({}, compileOptions, {
filename, filename,
name: capitalise(name), name: capitalise(name),
generate: 'ssr', generate: compileOptions.generate || "ssr",
format: 'cjs' format: "cjs",
}); });
const { js, warnings } = compile(fs.readFileSync(filename, 'utf-8'), options); const { js, css, ast, warnings, vars, stats } = compile(
fs.readFileSync(filename, "utf-8"),
options
);
if (options.dev) { if (options.dev) {
warnings.forEach(warning => { warnings.forEach((warning) => {
console.warn(`\nSvelte Warning in ${warning.filename}:`); console.warn(`\nSvelte Warning in ${warning.filename}:`);
console.warn(warning.message); console.warn(warning.message);
console.warn(warning.frame); console.warn(warning.frame);
}) });
} }
return module._compile(js.code, filename); let compiled = module._compile(js.code, filename);
return compiled;
}; };
} }
registerExtension('.svelte'); registerExtension(".svelte");
registerExtension('.html'); registerExtension(".html");
// To handle mixture of CJS and ESM modules .
require.extensions[".mjs"] = function (module, filename) {
try {
if (es6Compiler == null) {
throw `error es6Compiler is not set`;
}
let code = compileES6(fs.readFileSync(filename, "utf-8")).code;
return module._compile(code, filename);
} catch (error) {
console.log(`error ${error}`);
}
};
module.exports = register; module.exports = register;

@ -1,11 +1,18 @@
import Block from './Block'; import Block from "./Block";
import { CompileOptions, Var } from '../../interfaces'; import { CompileOptions, Var } from "../../interfaces";
import Component from '../Component'; import Component from "../Component";
import FragmentWrapper from './wrappers/Fragment'; import FragmentWrapper from "./wrappers/Fragment";
import { x } from 'code-red'; import { x } from "code-red";
import { Node, Identifier, MemberExpression, Literal, Expression, BinaryExpression } from 'estree'; import {
import flatten_reference from '../utils/flatten_reference'; Node,
import { reserved_keywords } from '../utils/reserved_keywords'; Identifier,
MemberExpression,
Literal,
Expression,
BinaryExpression,
} from "estree";
import flatten_reference from "../utils/flatten_reference";
import { reserved_keywords } from "../utils/reserved_keywords";
interface ContextMember { interface ContextMember {
name: string; name: string;
@ -32,7 +39,15 @@ export default class Renderer {
blocks: Array<Block | Node | Node[]> = []; blocks: Array<Block | Node | Node[]> = [];
readonly: Set<string> = new Set(); readonly: Set<string> = new Set();
meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag meta_bindings: Array<Node | Node[]> = []; // initial values for e.g. window.innerWidth, if there's a <svelte:window> meta tag
binding_groups: Map<string, { binding_group: (to_reference?: boolean) => Node; is_context: boolean; contexts: string[]; index: number }> = new Map(); binding_groups: Map<
string,
{
binding_group: (to_reference?: boolean) => Node;
is_context: boolean;
contexts: string[];
index: number;
}
> = new Map();
block: Block; block: Block;
fragment: FragmentWrapper; fragment: FragmentWrapper;
@ -45,33 +60,37 @@ export default class Renderer {
this.options = options; this.options = options;
this.locate = component.locate; // TODO messy this.locate = component.locate; // TODO messy
this.file_var = options.dev && this.component.get_unique_name('file'); this.file_var = options.dev && this.component.get_unique_name("file");
component.vars.filter(v => !v.hoistable || (v.export_name && !v.module)).forEach(v => this.add_to_context(v.name)); component.vars
.filter((v) => !v.hoistable || (v.export_name && !v.module))
.forEach((v) => this.add_to_context(v.name));
// ensure store values are included in context // ensure store values are included in context
component.vars.filter(v => v.subscribable).forEach(v => this.add_to_context(`$${v.name}`)); component.vars
.filter((v) => v.subscribable)
.forEach((v) => this.add_to_context(`$${v.name}`));
reserved_keywords.forEach(keyword => { reserved_keywords.forEach((keyword) => {
if (component.var_lookup.has(keyword)) { if (component.var_lookup.has(keyword)) {
this.add_to_context(keyword); this.add_to_context(keyword);
} }
}); });
if (component.slots.size > 0) { if (component.slots.size > 0) {
this.add_to_context('$$scope'); this.add_to_context("$$scope");
this.add_to_context('$$slots'); this.add_to_context("$$slots");
} }
if (this.binding_groups.size > 0) { if (this.binding_groups.size > 0) {
this.add_to_context('$$binding_groups'); this.add_to_context("$$binding_groups");
} }
// main block // main block
this.block = new Block({ this.block = new Block({
renderer: this, renderer: this,
name: null, name: null,
type: 'component', type: "component",
key: null, key: null,
bindings: new Map(), bindings: new Map(),
@ -91,7 +110,7 @@ export default class Renderer {
); );
// TODO messy // TODO messy
this.blocks.forEach(block => { this.blocks.forEach((block) => {
if (block instanceof Block) { if (block instanceof Block) {
block.assign_variable_names(); block.assign_variable_names();
} }
@ -103,7 +122,7 @@ export default class Renderer {
this.context_overflow = this.context.length > 31; this.context_overflow = this.context.length > 31;
this.context.forEach(member => { this.context.forEach((member) => {
const { variable } = member; const { variable } = member;
if (variable) { if (variable) {
member.priority += 2; member.priority += 2;
@ -124,8 +143,12 @@ export default class Renderer {
} }
}); });
this.context.sort((a, b) => (b.priority - a.priority) || ((a.index.value as number) - (b.index.value as number))); this.context.sort(
this.context.forEach((member, i) => member.index.value = i); (a, b) =>
b.priority - a.priority ||
(a.index.value as number) - (b.index.value as number)
);
this.context.forEach((member, i) => (member.index.value = i));
let i = this.context.length; let i = this.context.length;
while (i--) { while (i--) {
@ -143,11 +166,11 @@ export default class Renderer {
if (!this.context_lookup.has(name)) { if (!this.context_lookup.has(name)) {
const member: ContextMember = { const member: ContextMember = {
name, name,
index: { type: 'Literal', value: this.context.length }, // index is updated later, but set here to preserve order within groups index: { type: "Literal", value: this.context.length }, // index is updated later, but set here to preserve order within groups
is_contextual: false, is_contextual: false,
is_non_contextual: false, // shadowed vars could be contextual and non-contextual is_non_contextual: false, // shadowed vars could be contextual and non-contextual
variable: null, variable: null,
priority: 0 priority: 0,
}; };
this.context_lookup.set(name, member); this.context_lookup.set(name, member);
@ -171,23 +194,27 @@ export default class Renderer {
const variable = this.component.var_lookup.get(name); const variable = this.component.var_lookup.get(name);
const member = this.context_lookup.get(name); const member = this.context_lookup.get(name);
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) { if (
return x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${value || name}))`; variable &&
variable.subscribable &&
(variable.reassigned || variable.export_name)
) {
return x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${
value || name
}))`;
} }
if (name[0] === '$' && name[1] !== '$') { if (name[0] === "$" && name[1] !== "$") {
return x`${name.slice(1)}.set(${value || name})`; return x`${name.slice(1)}.set(${value || name})`;
} }
if ( if (
variable && ( variable &&
variable.module || ( (variable.module ||
!variable.referenced && (!variable.referenced &&
!variable.is_reactive_dependency && !variable.is_reactive_dependency &&
!variable.export_name && !variable.export_name &&
!name.startsWith('$$') !name.startsWith("$$")))
)
)
) { ) {
return value || name; return value || name;
} }
@ -199,32 +226,32 @@ export default class Renderer {
// if this is a reactive declaration, invalidate dependencies recursively // if this is a reactive declaration, invalidate dependencies recursively
const deps = new Set([name]); const deps = new Set([name]);
deps.forEach(name => { deps.forEach((name) => {
const reactive_declarations = this.component.reactive_declarations.filter(x => const reactive_declarations = this.component.reactive_declarations.filter(
x.assignees.has(name) (x) => x.assignees.has(name)
); );
reactive_declarations.forEach(declaration => { reactive_declarations.forEach((declaration) => {
declaration.dependencies.forEach(name => { declaration.dependencies.forEach((name) => {
deps.add(name); deps.add(name);
}); });
}); });
}); });
// TODO ideally globals etc wouldn't be here in the first place // TODO ideally globals etc wouldn't be here in the first place
const filtered = Array.from(deps).filter(n => this.context_lookup.has(n)); const filtered = Array.from(deps).filter((n) => this.context_lookup.has(n));
if (!filtered.length) return null; if (!filtered.length) return null;
return filtered return filtered
.map(n => x`$$invalidate(${this.context_lookup.get(n).index}, ${n})`) .map((n) => x`$$invalidate(${this.context_lookup.get(n).index}, ${n})`)
.reduce((lhs, rhs) => x`${lhs}, ${rhs}`); .reduce((lhs, rhs) => x`${lhs}, ${rhs}`);
} }
dirty(names, is_reactive_declaration = false): Expression { dirty(names, is_reactive_declaration = false): Expression {
const renderer = this; const renderer = this;
const dirty = (is_reactive_declaration const dirty = (is_reactive_declaration ? x`$$self.$$.dirty` : x`#dirty`) as
? x`$$self.$$.dirty` | Identifier
: x`#dirty`) as Identifier | MemberExpression; | MemberExpression;
const get_bitmask = () => { const get_bitmask = () => {
const bitmask: BitMasks = []; const bitmask: BitMasks = [];
@ -239,7 +266,7 @@ export default class Renderer {
const value = member.index.value as number; const value = member.index.value as number;
const i = (value / 31) | 0; const i = (value / 31) | 0;
const n = 1 << (value % 31); const n = 1 << value % 31;
if (!bitmask[i]) bitmask[i] = { n: 0, names: [] }; if (!bitmask[i]) bitmask[i] = { n: 0, names: [] };
@ -255,30 +282,34 @@ export default class Renderer {
// the expression lazily. TODO would be better if // the expression lazily. TODO would be better if
// context was determined before rendering, so that // context was determined before rendering, so that
// this indirection was unnecessary // this indirection was unnecessary
type: 'ParenthesizedExpression', type: "ParenthesizedExpression",
get expression() { get expression() {
const bitmask = get_bitmask(); const bitmask = get_bitmask();
if (!bitmask.length) { if (!bitmask.length) {
return x`${dirty} & /*${names.join(', ')}*/ 0` as BinaryExpression; return x`${dirty} & /*${names.join(", ")}*/ 0` as BinaryExpression;
} }
if (renderer.context_overflow) { if (renderer.context_overflow) {
return bitmask return bitmask
.map((b, i) => ({ b, i })) .map((b, i) => ({ b, i }))
.filter(({ b }) => b) .filter(({ b }) => b)
.map(({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(', ')}*/ ${b.n}`) .map(
({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(", ")}*/ ${b.n}`
)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`); .reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
} }
return x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0].n}` as BinaryExpression; return x`${dirty} & /*${names.join(", ")}*/ ${
} bitmask[0].n
}` as BinaryExpression;
},
} as any; } as any;
} }
reference(node: string | Identifier | MemberExpression) { reference(node: string | Identifier | MemberExpression) {
if (typeof node === 'string') { if (typeof node === "string") {
node = { type: 'Identifier', name: node }; node = { type: "Identifier", name: node };
} }
const { name, nodes } = flatten_reference(node); const { name, nodes } = flatten_reference(node);

@ -1,6 +1,6 @@
import { set_current_component, current_component } from './lifecycle'; import { set_current_component, current_component } from "./lifecycle";
import { run_all, blank_object } from './utils'; import { run_all, blank_object } from "./utils";
import { boolean_attributes } from '../../compiler/compile/render_ssr/handlers/shared/boolean_attributes'; import { boolean_attributes } from "../../compiler/compile/render_ssr/handlers/shared/boolean_attributes";
export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u; export const invalid_attribute_name_character = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
@ -12,12 +12,12 @@ export function spread(args, classes_to_add) {
if (attributes.class == null) { if (attributes.class == null) {
attributes.class = classes_to_add; attributes.class = classes_to_add;
} else { } else {
attributes.class += ' ' + classes_to_add; attributes.class += " " + classes_to_add;
} }
} }
let str = ''; let str = "";
Object.keys(attributes).forEach(name => { Object.keys(attributes).forEach((name) => {
if (invalid_attribute_name_character.test(name)) return; if (invalid_attribute_name_character.test(name)) return;
const value = attributes[name]; const value = attributes[name];
@ -25,7 +25,9 @@ export function spread(args, classes_to_add) {
else if (boolean_attributes.has(name.toLowerCase())) { else if (boolean_attributes.has(name.toLowerCase())) {
if (value) str += " " + name; if (value) str += " " + name;
} else if (value != null) { } else if (value != null) {
str += ` ${name}="${String(value).replace(/"/g, '&#34;').replace(/'/g, '&#39;')}"`; str += ` ${name}="${String(value)
.replace(/"/g, "&#34;")
.replace(/'/g, "&#39;")}"`;
} }
}); });
@ -33,19 +35,19 @@ export function spread(args, classes_to_add) {
} }
export const escaped = { export const escaped = {
'"': '&quot;', '"': "&quot;",
"'": '&#39;', "'": "&#39;",
'&': '&amp;', "&": "&amp;",
'<': '&lt;', "<": "&lt;",
'>': '&gt;' ">": "&gt;",
}; };
export function escape(html) { export function escape(html) {
return String(html).replace(/["'&<>]/g, match => escaped[match]); return String(html).replace(/["'&<>]/g, (match) => escaped[match]);
} }
export function each(items, fn) { export function each(items, fn) {
let str = ''; let str = "";
for (let i = 0; i < items.length; i += 1) { for (let i = 0; i < items.length; i += 1) {
str += fn(items[i], i); str += fn(items[i], i);
} }
@ -53,22 +55,25 @@ export function each(items, fn) {
} }
export const missing_component = { export const missing_component = {
$$render: () => '' $$render: () => "",
}; };
export function validate_component(component, name) { export function validate_component(component, name) {
if (component.default) component = component.default;
if (!component || !component.$$render) { if (!component || !component.$$render) {
if (name === 'svelte:component') name += ' this={...}'; if (name === "svelte:component") name += " this={...}";
throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`); throw new Error(
`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`
);
} }
return component; return component;
} }
export function debug(file, line, column, values) { export function debug(file, line, column, values) {
console.log(`{@debug} ${file ? file + ' ' : ''}(${line}:${column})`); // eslint-disable-line no-console console.log(`{@debug} ${file ? file + " " : ""}(${line}:${column})`); // eslint-disable-line no-console
console.log(values); // eslint-disable-line no-console console.log(values); // eslint-disable-line no-console
return ''; return "";
} }
let on_destroy; let on_destroy;
@ -85,7 +90,7 @@ export function create_ssr_component(fn) {
on_mount: [], on_mount: [],
before_update: [], before_update: [],
after_update: [], after_update: [],
callbacks: blank_object() callbacks: blank_object(),
}; };
set_current_component({ $$ }); set_current_component({ $$ });
@ -107,7 +112,7 @@ export function create_ssr_component(fn) {
map: null; map: null;
code: string; code: string;
}>; }>;
} = { title: '', head: '', css: new Set() }; } = { title: "", head: "", css: new Set() };
const html = $$render(result, props, {}, options); const html = $$render(result, props, {}, options);
@ -116,20 +121,30 @@ export function create_ssr_component(fn) {
return { return {
html, html,
css: { css: {
code: Array.from(result.css).map(css => css.code).join('\n'), code: Array.from(result.css)
map: null // TODO .map((css) => css.code)
.join("\n"),
map: null, // TODO
}, },
head: result.title + result.head head: result.title + result.head,
}; };
}, },
$$render $$render,
}; };
} }
export function add_attribute(name, value, boolean) { export function add_attribute(name, value, boolean) {
if (value == null || (boolean && !value)) return ''; if (value == null || (boolean && !value)) return "";
return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`}`; return ` ${name}${
value === true
? ""
: `=${
typeof value === "string"
? JSON.stringify(escape(value))
: `"${value}"`
}`
}`;
} }
export function add_classes(classes) { export function add_classes(classes) {

Loading…
Cancel
Save