Skip to content
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

Initial test of vite integration #1550

Merged
merged 46 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f61a4e2
sketching vite packages
ef4 Jul 1, 2023
9a0d938
stubbed out the start of a vite plugin
ef4 Jul 3, 2023
7b2cb53
renaming
ef4 Jul 4, 2023
9495832
this is going to start out as a real app, not a template
ef4 Jul 5, 2023
bd438ff
generating fresh ember app
ef4 Jul 5, 2023
6ecfea0
iterating on first vite resolver
ef4 Jul 5, 2023
e732297
more progress
ef4 Jul 6, 2023
0bd5fe6
making tests work too
ef4 Jul 6, 2023
c45b8e0
keep the original tests/index.html and put our rewritten one next to it
ef4 Jul 6, 2023
bcb094d
Merge branch 'main' into vite-app
ef4 Jul 14, 2023
e3d7289
dropping unused dir
ef4 Jul 14, 2023
894ca83
adjust vite root
ef4 Jul 14, 2023
2e1263f
don't need server here
ef4 Jul 14, 2023
6165ad2
guard against re-entering resolver plugin from hbs plugin
ef4 Jul 15, 2023
8b2d32e
Merge branch 'main' into vite-app
ef4 Jul 16, 2023
540f349
post-merge update
ef4 Jul 16, 2023
eeefa47
adjusting naming scheme to be easier to work with in rollup+vite
ef4 Jul 16, 2023
011ef83
use staticEmberSource
ef4 Jul 16, 2023
5dbfd2c
introducing option for amdCompatibility mode control
ef4 Jul 16, 2023
7ed68e2
get lints and tests back toward passing
ef4 Jul 16, 2023
54dc2d0
updating some tests to match changes implementation paths
ef4 Jul 16, 2023
7107ad4
move eager modules before amdModules
ef4 Jul 16, 2023
85add48
updating lockfile and re-adding ember-data to vite-app
ef4 Jul 16, 2023
21fc581
we can put the root in the config file instead of the startup script
ef4 Jul 16, 2023
a1d7bb6
allow testing through `vite build`
ef4 Jul 16, 2023
a6ecfcf
Merge branch 'main' into vite-app
ef4 Jul 17, 2023
ab883bc
Merge branch 'peer-deps-check' into vite-app
ef4 Jul 17, 2023
51b499f
regenerating lockfile
ef4 Jul 17, 2023
2a26a67
unintentionally leftover
ef4 Jul 17, 2023
8148dea
production script handling for the traditional scripts
ef4 Jul 18, 2023
4f40fbb
adjusting a type
ef4 Jul 18, 2023
121008f
Merge branch 'fix-helper-compat' into vite-app
ef4 Jul 18, 2023
d842346
better error message
ef4 Jul 18, 2023
942f70f
Merge branch 'rehome-to-real-files' into vite-app
ef4 Jul 18, 2023
9a86b97
better error message
ef4 Jul 18, 2023
0049171
vite request meta support
ef4 Jul 18, 2023
1e4be16
adding gjs example
ef4 Jul 18, 2023
ebfd4ac
defend against vite query params in filenames in babel
ef4 Jul 18, 2023
7a6673d
suppressing eslint prettier issue
ef4 Jul 18, 2023
ef0daf2
exclude addons from optimizeDeps
ef4 Jul 18, 2023
f7beedd
Merge branch 'main' into vite-app
ef4 Jul 18, 2023
39edf73
moving pre's up into the plugins
ef4 Jul 19, 2023
430fe22
smoke-testing ember data
ef4 Jul 20, 2023
a55f57f
adjusting example
ef4 Jul 20, 2023
e41e86c
Merge branch 'main' into vite-app
ef4 Jul 20, 2023
d024e58
disabling eslint for vite-test app
ef4 Jul 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this supposed to be comitted? 🙃

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This is working around the fact that our packages all compile to commonjs and the vite config is a real node ES module. Both the implementation and the types go through these top-level reexports, which happens to make the cjs interop work out ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone please do the big yak shave to get all our packages dual compiling as CJS and ESM so I don't have to. 😅

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