Skip to content

Commit

Permalink
Merge pull request #1550 from embroider-build/vite-app
Browse files Browse the repository at this point in the history
Initial test of vite integration
  • Loading branch information
ef4 authored Jul 20, 2023
2 parents 0e00f2b + d024e58 commit 7407f3a
Show file tree
Hide file tree
Showing 60 changed files with 3,683 additions and 1,209 deletions.
6 changes: 6 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
/packages/addon-dev/**/*.js
/packages/addon-dev/**/*.d.ts
/tests/fixtures/
/tests/vite-app/
/packages/vite/index.mjs
/packages/vite/index.d.ts
/packages/vite/src/**/*.js
/packages/vite/src/**/*.d.ts


# unconventional js
/blueprints/*/files/
Expand Down
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
/packages/addon-dev/**/*.js
/packages/addon-dev/**/*.d.ts
/tests/fixtures/
/packages/vite/index.mjs
/packages/vite/index.d.ts
/packages/vite/src/**/*.js
/packages/vite/src/**/*.d.ts


# unconventional js
/blueprints/*/files/
Expand Down
2 changes: 2 additions & 0 deletions packages/util/tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<h2 id="title">Welcome to Ember</h2>

{{@model.message}}

{{outlet}}
11 changes: 11 additions & 0 deletions packages/vite/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# compiled output
/src/**/*.js
/src/**/*.d.ts

# shims
/index.d.ts
/index.mjs

# dependencies
/node_modules/

7 changes: 7 additions & 0 deletions packages/vite/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/node_modules
/src/**/*.js
/src/**/*.d.ts
/src/**/*.map
/*/tests/**/*.js
/*/tests/**/*.d.ts
/*/tests/**/*.map
5 changes: 5 additions & 0 deletions packages/vite/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './src/resolver.js';
export * from './src/hbs.js';
export * from './src/scripts.js';
export * from './src/template-tag.js';
export * from './src/addons.js';
5 changes: 5 additions & 0 deletions packages/vite/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './src/resolver.js';
export * from './src/hbs.js';
export * from './src/scripts.js';
export * from './src/template-tag.js';
export * from './src/addons.js';
27 changes: 27 additions & 0 deletions packages/vite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@embroider/vite",
"version": "0.0.0",
"main": "index.mjs",
"peerDependencies": {
"@embroider/core": "workspace:^",
"vite": "^4.3.9"
},
"dependencies": {
"@rollup/pluginutils": "^4.1.1",
"assert-never": "^1.2.1",
"content-tag": "^1.0.0",
"debug": "^4.3.2",
"fs-extra": "^10.0.0",
"jsdom": "^16.6.0",
"source-map-url": "^0.4.1",
"terser": "^5.7.0"
},
"devDependencies": {
"@embroider/core": "workspace:^",
"@types/debug": "^4.1.5",
"@types/jsdom": "^16.2.11",
"@types/fs-extra": "^9.0.12",
"rollup": "^3.23.0",
"vite": "^4.3.9"
}
}
20 changes: 20 additions & 0 deletions packages/vite/src/addons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ResolverLoader, packageName } from '@embroider/core';

export function addons(root: string): string[] {
let rloader = new ResolverLoader(root);
let { options } = rloader.resolver;
let names = new Set<string>();
for (let from of Object.keys(options.renameModules)) {
let pName = packageName(from);
if (pName) {
names.add(pName);
}
}
for (let from of Object.keys(options.renamePackages)) {
names.add(from);
}
for (let name of Object.keys(options.activeAddons)) {
names.add(name);
}
return [...names];
}
129 changes: 129 additions & 0 deletions packages/vite/src/hbs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// TODO: I copied this from @embroider/addon-dev, it needs to be its own package
// (or be in shared-internals or core)
import { createFilter } from '@rollup/pluginutils';
import type { PluginContext, ResolvedId } from 'rollup';
import type { Plugin } from 'vite';
import { readFileSync } from 'fs';
import { hbsToJS } from '@embroider/core';
import assertNever from 'assert-never';
import { parse as pathParse } from 'path';
import makeDebug from 'debug';

const debug = makeDebug('embroider:hbs-plugin');

export function hbs(): Plugin {
return {
name: 'rollup-hbs-plugin',
enforce: 'pre',
async resolveId(source: string, importer: string | undefined) {
let resolution = await this.resolve(source, importer, {
skipSelf: true,
});

if (!resolution) {
return maybeSynthesizeComponentJS(this, source, importer);
} else {
return maybeRewriteHBS(resolution);
}
},

load(id: string) {
const meta = getMeta(this, id);
if (!meta) {
return;
}

switch (meta.type) {
case 'template':
let input = readFileSync(id, 'utf8');
let code = hbsToJS(input);
return {
code,
};
case 'template-only-component-js':
return {
code: templateOnlyComponent,
};
default:
assertNever(meta);
}
},
};
}

const templateOnlyComponent =
`import templateOnly from '@ember/component/template-only';\n` + `export default templateOnly();\n`;

type Meta =
| {
type: 'template';
}
| {
type: 'template-only-component-js';
};

function getMeta(context: PluginContext, id: string): Meta | null {
const meta = context.getModuleInfo(id)?.meta?.['rollup-hbs-plugin'];
if (meta) {
return meta as Meta;
} else {
return null;
}
}

function correspondingTemplate(filename: string): string {
let { ext } = pathParse(filename);
return filename.slice(0, filename.length - ext.length) + '.hbs';
}

async function maybeSynthesizeComponentJS(context: PluginContext, source: string, importer: string | undefined) {
debug(`checking for template-only component: %s`, source);
let templateResolution = await context.resolve(correspondingTemplate(source), importer, {
skipSelf: true,
custom: {
embroider: {
// we don't want to recurse into the whole embroider compatbility
// resolver here. It has presumably already steered our request to the
// correct place. All we want to do is slightly modify the request we
// were given (changing the extension) and check if that would resolve
// instead.
//
// Currently this guard is only actually exercised in rollup, not in
// vite, due to https://github.com/vitejs/vite/issues/13852
enableCustomResolver: false,
},
},
});
if (!templateResolution) {
return null;
}
debug(`emitting template only component: %s`, templateResolution.id);

// we're trying to resolve a JS module but only the corresponding HBS
// file exists. Synthesize the template-only component JS.
return {
id: templateResolution.id.replace(/\.hbs$/, '.js'),
meta: {
'rollup-hbs-plugin': {
type: 'template-only-component-js',
},
},
};
}

const hbsFilter = createFilter('**/*.hbs');

function maybeRewriteHBS(resolution: ResolvedId) {
if (!hbsFilter(resolution.id)) {
return null;
}
debug('emitting hbs rewrite: %s', resolution.id);
return {
...resolution,
meta: {
'rollup-hbs-plugin': {
type: 'template',
},
},
};
}
55 changes: 55 additions & 0 deletions packages/vite/src/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ModuleRequest, cleanUrl } from '@embroider/core';

export const virtualPrefix = 'embroider_virtual:';

export class RollupModuleRequest implements ModuleRequest {
static from(
source: string,
importer: string | undefined,
custom: Record<string, any> | undefined
): RollupModuleRequest | undefined {
if (!(custom?.embroider?.enableCustomResolver ?? true)) {
return;
}

if (source && importer && source[0] !== '\0') {
let nonVirtual: string;
if (importer.startsWith(virtualPrefix)) {
nonVirtual = importer.slice(virtualPrefix.length);
} else {
nonVirtual = importer;
}

// strip query params off the importer
let fromFile = cleanUrl(nonVirtual);
return new RollupModuleRequest(source, fromFile, custom?.embroider?.meta);
}
}

private constructor(
readonly specifier: string,
readonly fromFile: string,
readonly meta: Record<string, any> | undefined
) {}

get isVirtual(): boolean {
return this.specifier.startsWith(virtualPrefix);
}

alias(newSpecifier: string) {
return new RollupModuleRequest(newSpecifier, this.fromFile, this.meta) as this;
}
rehome(newFromFile: string) {
if (this.fromFile === newFromFile) {
return this;
} else {
return new RollupModuleRequest(this.specifier, newFromFile, this.meta) as this;
}
}
virtualize(filename: string) {
return new RollupModuleRequest(virtualPrefix + filename, this.fromFile, this.meta) as this;
}
withMeta(meta: Record<string, any> | undefined): this {
return new RollupModuleRequest(this.specifier, this.fromFile, meta) as this;
}
}
69 changes: 69 additions & 0 deletions packages/vite/src/resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { PluginContext, ResolveIdResult } from 'rollup';
import { Plugin } from 'vite';
import { join } from 'path';
import {
Resolution,
Resolver,
ResolverFunction,
ResolverOptions,
locateEmbroiderWorkingDir,
virtualContent,
} from '@embroider/core';
import { readJSONSync } from 'fs-extra';
import { RollupModuleRequest, virtualPrefix } from './request';
import assertNever from 'assert-never';

export function resolver(): Plugin {
let resolverOptions: ResolverOptions = readJSONSync(join(locateEmbroiderWorkingDir(process.cwd()), 'resolver.json'));
let resolver = new Resolver(resolverOptions);

return {
name: 'embroider-resolver',
enforce: 'pre',
async resolveId(source, importer, options) {
let request = RollupModuleRequest.from(source, importer, options.custom);
if (!request) {
// fallthrough to other rollup plugins
return null;
}
let resolution = await resolver.resolve(request, defaultResolve(this));
switch (resolution.type) {
case 'found':
return resolution.result;
case 'not_found':
return null;
default:
throw assertNever(resolution);
}
},
load(id) {
if (id.startsWith(virtualPrefix)) {
return virtualContent(id.slice(virtualPrefix.length), resolver);
}
},
};
}

function defaultResolve(context: PluginContext): ResolverFunction<RollupModuleRequest, Resolution<ResolveIdResult>> {
return async (request: RollupModuleRequest) => {
if (request.isVirtual) {
return {
type: 'found',
result: { id: request.specifier, resolvedBy: request.fromFile },
};
}
let result = await context.resolve(request.specifier, request.fromFile, {
skipSelf: true,
custom: {
embroider: {
meta: request.meta,
},
},
});
if (result) {
return { type: 'found', result };
} else {
return { type: 'not_found', err: undefined };
}
};
}
Loading

0 comments on commit 7407f3a

Please sign in to comment.