Skip to content

Commit 3af7a7c

Browse files
authored
Merge pull request #447 from sveltejs/gh-440
Remove unnecessary template IIFEs
2 parents 3881f5f + 3fcbf42 commit 3af7a7c

File tree

7 files changed

+210
-132
lines changed

7 files changed

+210
-132
lines changed

src/generators/Generator.js

Lines changed: 105 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import isReference from '../utils/isReference.js';
44
import flattenReference from '../utils/flattenReference.js';
55
import globalWhitelist from '../utils/globalWhitelist.js';
66
import reservedNames from '../utils/reservedNames.js';
7+
import namespaces from '../utils/namespaces.js';
8+
import { removeNode, removeObjectKey } from '../utils/removeNode.js';
79
import getIntro from './shared/utils/getIntro.js';
810
import getOutro from './shared/utils/getOutro.js';
911
import processCss from './shared/processCss.js';
@@ -21,6 +23,7 @@ export default class Generator {
2123
this.helpers = new Set();
2224
this.components = new Set();
2325
this.events = new Set();
26+
this.importedComponents = new Map();
2427

2528
this.bindingGroups = [];
2629

@@ -257,67 +260,42 @@ export default class Generator {
257260
};
258261
}
259262

260-
parseJs () {
263+
parseJs ( ssr ) {
261264
const { source } = this;
262265
const { js } = this.parsed;
263266

264267
const imports = this.imports;
265268
const computations = [];
266-
let defaultExport = null;
267269
const templateProperties = {};
268270

271+
let namespace = null;
272+
let hasJs = !!js;
273+
269274
if ( js ) {
270275
this.addSourcemapLocations( js.content );
276+
const body = js.content.body.slice(); // slice, because we're going to be mutating the original
271277

272278
// imports need to be hoisted out of the IIFE
273-
for ( let i = 0; i < js.content.body.length; i += 1 ) {
274-
const node = js.content.body[i];
279+
for ( let i = 0; i < body.length; i += 1 ) {
280+
const node = body[i];
275281
if ( node.type === 'ImportDeclaration' ) {
276-
let a = node.start;
277-
let b = node.end;
278-
while ( /[ \t]/.test( source[ a - 1 ] ) ) a -= 1;
279-
while ( source[b] === '\n' ) b += 1;
280-
282+
removeNode( this.code, js.content, node );
281283
imports.push( node );
282-
this.code.remove( a, b );
284+
283285
node.specifiers.forEach( specifier => {
284286
this.importedNames.add( specifier.local.name );
285287
});
286288
}
287289
}
288290

289-
defaultExport = js.content.body.find( node => node.type === 'ExportDefaultDeclaration' );
291+
const defaultExport = body.find( node => node.type === 'ExportDefaultDeclaration' );
290292

291293
if ( defaultExport ) {
292-
const finalNode = js.content.body[ js.content.body.length - 1 ];
293-
if ( defaultExport === finalNode ) {
294-
// export is last property, we can just return it
295-
this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `return ` );
296-
} else {
297-
const { declarations } = annotateWithScopes( js );
298-
let template = 'template';
299-
for ( let i = 1; declarations.has( template ); template = `template_${i++}` );
300-
301-
this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `var ${template} = ` );
302-
303-
let i = defaultExport.start;
304-
while ( /\s/.test( source[ i - 1 ] ) ) i--;
305-
306-
const indentation = source.slice( i, defaultExport.start );
307-
this.code.appendLeft( finalNode.end, `\n\n${indentation}return ${template};` );
308-
}
309-
310294
defaultExport.declaration.properties.forEach( prop => {
311295
templateProperties[ prop.key.name ] = prop;
312296
});
313-
314-
this.code.prependRight( js.content.start, `var ${this.alias( 'template' )} = (function () {` );
315-
} else {
316-
this.code.prependRight( js.content.start, '(function () {' );
317297
}
318298

319-
this.code.appendLeft( js.content.end, '}());' );
320-
321299
[ 'helpers', 'events', 'components' ].forEach( key => {
322300
if ( templateProperties[ key ] ) {
323301
templateProperties[ key ].value.properties.forEach( prop => {
@@ -353,11 +331,102 @@ export default class Generator {
353331

354332
templateProperties.computed.value.properties.forEach( prop => visit( prop.key.name ) );
355333
}
334+
335+
if ( templateProperties.namespace ) {
336+
const ns = templateProperties.namespace.value.value;
337+
namespace = namespaces[ ns ] || ns;
338+
339+
removeObjectKey( this.code, defaultExport.declaration, 'namespace' );
340+
}
341+
342+
if ( templateProperties.components ) {
343+
let hasNonImportedComponent = false;
344+
templateProperties.components.value.properties.forEach( property => {
345+
const key = property.key.name;
346+
const value = source.slice( property.value.start, property.value.end );
347+
if ( this.importedNames.has( value ) ) {
348+
this.importedComponents.set( key, value );
349+
} else {
350+
hasNonImportedComponent = true;
351+
}
352+
});
353+
if ( hasNonImportedComponent ) {
354+
// remove the specific components that were imported, as we'll refer to them directly
355+
Array.from( this.importedComponents.keys() ).forEach( key => {
356+
removeObjectKey( this.code, templateProperties.components.value, key );
357+
});
358+
} else {
359+
// remove the entire components portion of the export
360+
removeObjectKey( this.code, defaultExport.declaration, 'components' );
361+
}
362+
}
363+
364+
// Remove these after version 2
365+
if ( templateProperties.onrender ) {
366+
const { key } = templateProperties.onrender;
367+
this.code.overwrite( key.start, key.end, 'oncreate', true );
368+
templateProperties.oncreate = templateProperties.onrender;
369+
}
370+
371+
if ( templateProperties.onteardown ) {
372+
const { key } = templateProperties.onteardown;
373+
this.code.overwrite( key.start, key.end, 'ondestroy', true );
374+
templateProperties.ondestroy = templateProperties.onteardown;
375+
}
376+
377+
// in an SSR context, we don't need to include events, methods, oncreate or ondestroy
378+
if ( ssr ) {
379+
if ( templateProperties.oncreate ) removeNode( this.code, defaultExport.declaration, templateProperties.oncreate );
380+
if ( templateProperties.ondestroy ) removeNode( this.code, defaultExport.declaration, templateProperties.ondestroy );
381+
if ( templateProperties.methods ) removeNode( this.code, defaultExport.declaration, templateProperties.methods );
382+
if ( templateProperties.events ) removeNode( this.code, defaultExport.declaration, templateProperties.events );
383+
}
384+
385+
// now that we've analysed the default export, we can determine whether or not we need to keep it
386+
let hasDefaultExport = !!defaultExport;
387+
if ( defaultExport && defaultExport.declaration.properties.length === 0 ) {
388+
hasDefaultExport = false;
389+
removeNode( this.code, js.content, defaultExport );
390+
}
391+
392+
// if we do need to keep it, then we need to generate a return statement
393+
if ( hasDefaultExport ) {
394+
const finalNode = body[ body.length - 1 ];
395+
if ( defaultExport === finalNode ) {
396+
// export is last property, we can just return it
397+
this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `return ` );
398+
} else {
399+
const { declarations } = annotateWithScopes( js );
400+
let template = 'template';
401+
for ( let i = 1; declarations.has( template ); template = `template_${i++}` );
402+
403+
this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `var ${template} = ` );
404+
405+
let i = defaultExport.start;
406+
while ( /\s/.test( source[ i - 1 ] ) ) i--;
407+
408+
const indentation = source.slice( i, defaultExport.start );
409+
this.code.appendLeft( finalNode.end, `\n\n${indentation}return ${template};` );
410+
}
411+
}
412+
413+
// user code gets wrapped in an IIFE
414+
if ( js.content.body.length ) {
415+
const prefix = hasDefaultExport ? `var ${this.alias( 'template' )} = (function () {` : `(function () {`;
416+
this.code.prependRight( js.content.start, prefix ).appendLeft( js.content.end, '}());' );
417+
}
418+
419+
// if there's no need to include user code, remove it altogether
420+
else {
421+
this.code.remove( js.content.start, js.content.end );
422+
hasJs = false;
423+
}
356424
}
357425

358426
return {
359427
computations,
360-
defaultExport,
428+
hasJs,
429+
namespace,
361430
templateProperties
362431
};
363432
}

src/generators/dom/index.js

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import deindent from '../../utils/deindent.js';
22
import getBuilders from './utils/getBuilders.js';
33
import CodeBuilder from '../../utils/CodeBuilder.js';
4-
import namespaces from '../../utils/namespaces.js';
5-
import removeObjectKey from '../../utils/removeObjectKey.js';
64
import visitors from './visitors/index.js';
75
import Generator from '../Generator.js';
86
import * as shared from '../../shared/index.js';
@@ -17,8 +15,6 @@ class DomGenerator extends Generator {
1715
this.builders = {
1816
metaBindings: new CodeBuilder()
1917
};
20-
21-
this.importedComponents = new Map();
2218
}
2319

2420
addElement ( name, renderStatement, needsIdentifier = false ) {
@@ -155,50 +151,7 @@ export default function dom ( parsed, source, options ) {
155151

156152
const generator = new DomGenerator( parsed, source, name, visitors, options );
157153

158-
const { computations, defaultExport, templateProperties } = generator.parseJs();
159-
160-
// Remove these after version 2
161-
if ( templateProperties.onrender ) {
162-
const { key } = templateProperties.onrender;
163-
generator.code.overwrite( key.start, key.end, 'oncreate', true );
164-
templateProperties.oncreate = templateProperties.onrender;
165-
}
166-
167-
if ( templateProperties.onteardown ) {
168-
const { key } = templateProperties.onteardown;
169-
generator.code.overwrite( key.start, key.end, 'ondestroy', true );
170-
templateProperties.ondestroy = templateProperties.onteardown;
171-
}
172-
173-
let namespace = null;
174-
if ( templateProperties.namespace ) {
175-
const ns = templateProperties.namespace.value.value;
176-
namespace = namespaces[ ns ] || ns;
177-
178-
removeObjectKey( generator, defaultExport.declaration, 'namespace' );
179-
}
180-
181-
if ( templateProperties.components ) {
182-
let hasNonImportedComponent = false;
183-
templateProperties.components.value.properties.forEach( property => {
184-
const key = property.key.name;
185-
const value = source.slice( property.value.start, property.value.end );
186-
if ( generator.importedNames.has( value ) ) {
187-
generator.importedComponents.set( key, value );
188-
} else {
189-
hasNonImportedComponent = true;
190-
}
191-
});
192-
if ( hasNonImportedComponent ) {
193-
// remove the specific components that were imported, as we'll refer to them directly
194-
Array.from( generator.importedComponents.keys() ).forEach( key => {
195-
removeObjectKey( generator, templateProperties.components.value, key );
196-
});
197-
} else {
198-
// remove the entire components portion of the export
199-
removeObjectKey( generator, defaultExport.declaration, 'components' );
200-
}
201-
}
154+
const { computations, hasJs, templateProperties, namespace } = generator.parseJs();
202155

203156
const getUniqueName = generator.getUniqueNameMaker( [ 'root' ] );
204157
const component = getUniqueName( 'component' );
@@ -273,7 +226,7 @@ export default function dom ( parsed, source, options ) {
273226
${generator.helper( 'dispatchObservers' )}( this, this._observers.post, newState, oldState );
274227
` );
275228

276-
if ( parsed.js ) {
229+
if ( hasJs ) {
277230
builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` );
278231
}
279232

src/generators/server-side-rendering/index.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default function ssr ( parsed, source, options ) {
4040

4141
const generator = new SsrGenerator( parsed, source, name, visitors, options );
4242

43-
const { computations, templateProperties } = generator.parseJs();
43+
const { computations, hasJs, templateProperties } = generator.parseJs( true );
4444

4545
const builders = {
4646
main: new CodeBuilder(),
@@ -117,7 +117,9 @@ export default function ssr ( parsed, source, options ) {
117117
` );
118118

119119
templateProperties.components.value.properties.forEach( prop => {
120-
builders.renderCss.addLine( `addComponent( ${generator.alias( 'template' )}.components.${prop.key.name} );` );
120+
const { name } = prop.key;
121+
const expression = generator.importedComponents.get( name ) || `${generator.alias( 'template' )}.components.${name}`;
122+
builders.renderCss.addLine( `addComponent( ${expression} );` );
121123
});
122124
}
123125

@@ -129,7 +131,7 @@ export default function ssr ( parsed, source, options ) {
129131
};
130132
` );
131133

132-
if ( parsed.js ) {
134+
if ( hasJs ) {
133135
builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` );
134136
}
135137

src/generators/server-side-rendering/visitors/Component.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default {
5050
}))
5151
.join( ', ' );
5252

53-
const expression = node.name === ':Self' ? generator.name : `${generator.alias( 'template' )}.components.${node.name}`;
53+
const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get( node.name ) || `${generator.alias( 'template' )}.components.${node.name}`;
5454

5555
bindings.forEach( binding => {
5656
generator.addBinding( binding, expression );

src/utils/removeNode.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const keys = {
2+
ObjectExpression: 'properties',
3+
Program: 'body'
4+
};
5+
6+
const offsets = {
7+
ObjectExpression: [ 1, -1 ],
8+
Program: [ 0, 0 ]
9+
};
10+
11+
export function removeNode ( code, parent, node ) {
12+
const key = keys[ parent.type ];
13+
const offset = offsets[ parent.type ];
14+
if ( !key || !offset ) throw new Error( `not implemented: ${parent.type}` );
15+
16+
const list = parent[ key ];
17+
const i = list.indexOf( node );
18+
if ( i === -1 ) throw new Error( 'node not in list' );
19+
20+
let a;
21+
let b;
22+
23+
if ( list.length === 1 ) {
24+
// remove everything, leave {}
25+
a = parent.start + offset[0];
26+
b = parent.end + offset[1];
27+
} else if ( i === 0 ) {
28+
// remove everything before second node, including comments
29+
a = parent.start + offset[0];
30+
while ( /\s/.test( code.original[a] ) ) a += 1;
31+
32+
b = list[i].end;
33+
while ( /[\s,]/.test( code.original[b] ) ) b += 1;
34+
} else {
35+
// remove the end of the previous node to the end of this one
36+
a = list[ i - 1 ].end;
37+
b = node.end;
38+
}
39+
40+
code.remove( a, b );
41+
list.splice( i, 1 );
42+
return;
43+
}
44+
45+
export function removeObjectKey ( code, node, key ) {
46+
if ( node.type !== 'ObjectExpression' ) return;
47+
48+
let i = node.properties.length;
49+
while ( i-- ) {
50+
const property = node.properties[i];
51+
if ( property.key.type === 'Identifier' && property.key.name === key ) {
52+
removeNode( code, node, property );
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)