diff --git a/.eslintignore b/.eslintignore index e2b411191..5e36a6d05 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,6 +12,10 @@ /packages/webpack/**/*.d.ts /packages/hbs-loader/**/*.js /packages/hbs-loader/**/*.d.ts +/packages/rollup-plugin-hbs/**/*.js +/packages/rollup-plugin-hbs/**/*.d.ts +/packages/vite/**/*.js +/packages/vite/**/*.d.ts /test-packages/support/**/*.js /test-packages/**/*.d.ts /tests/scenarios/**/*.js diff --git a/packages/rollup-plugin-hbs/.gitignore b/packages/rollup-plugin-hbs/.gitignore new file mode 100644 index 000000000..68c2b31d0 --- /dev/null +++ b/packages/rollup-plugin-hbs/.gitignore @@ -0,0 +1,7 @@ +/node_modules +/src/**/*.js +/src/**/*.d.ts +/src/**/*.map +/*/tests/**/*.js +/*/tests/**/*.d.ts +/*/tests/**/*.map diff --git a/packages/rollup-plugin-hbs/package.json b/packages/rollup-plugin-hbs/package.json new file mode 100644 index 000000000..1270ab428 --- /dev/null +++ b/packages/rollup-plugin-hbs/package.json @@ -0,0 +1,36 @@ +{ + "name": "@embroider/rollup-plugin-hbs", + "version": "0.42.3", + "private": false, + "description": "Glimmer handlebars plugin for Rollup", + "repository": { + "type": "git", + "url": "https://github.com/embroider-build/embroider.git", + "directory": "packages/rollup-plugin-hbs" + }, + "license": "MIT", + "author": "Alex LaFroscia", + "main": "src/index.js", + "files": [ + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.js.map" + ], + "scripts": { + "prepare": "tsc" + }, + "peerDependencies": { + "@embroider/core": "0.42.3" + }, + "devDependencies": { + "@embroider/core": "0.42.3", + "rollup": "^2.52.6", + "typescript": "~4.0.0" + }, + "engines": { + "node": "10.* || 12.* || >= 14" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/rollup-plugin-hbs/src/index.ts b/packages/rollup-plugin-hbs/src/index.ts new file mode 100644 index 000000000..1309a882a --- /dev/null +++ b/packages/rollup-plugin-hbs/src/index.ts @@ -0,0 +1,25 @@ +import { parse } from 'path'; +import type { Plugin as RollupPlugin } from 'rollup'; +import { applyVariantToTemplateCompiler, Variant } from '@embroider/core'; + +export interface Options { + templateCompilerFile: string; + variant: Variant; +} + +export default function glimmerTemplateCompilerPlugin({ templateCompilerFile, variant }: Options): RollupPlugin { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const templateCompiler = applyVariantToTemplateCompiler(variant, require(templateCompilerFile)).compile; + + return { + name: '@embroider/rollup-plugin-hbs', + + transform(src, id) { + const parsedFilePath = parse(id); + + if (parsedFilePath.ext === '.hbs') { + return templateCompiler(id, src); + } + }, + }; +} diff --git a/packages/vite/.gitignore b/packages/vite/.gitignore new file mode 100644 index 000000000..68c2b31d0 --- /dev/null +++ b/packages/vite/.gitignore @@ -0,0 +1,7 @@ +/node_modules +/src/**/*.js +/src/**/*.d.ts +/src/**/*.map +/*/tests/**/*.js +/*/tests/**/*.d.ts +/*/tests/**/*.map diff --git a/packages/vite/package.json b/packages/vite/package.json new file mode 100644 index 000000000..f92982adb --- /dev/null +++ b/packages/vite/package.json @@ -0,0 +1,42 @@ +{ + "name": "@embroider/vite", + "version": "0.42.3", + "private": false, + "description": "Builds EmberJS apps with Vite", + "repository": { + "type": "git", + "url": "https://github.com/embroider-build/embroider.git", + "directory": "packages/vite" + }, + "license": "MIT", + "author": "Alex LaFroscia", + "main": "src/index.js", + "files": [ + "src/**/*.js", + "src/**/*.d.ts", + "src/**/*.js.map" + ], + "scripts": { + "prepare": "tsc" + }, + "dependencies": { + "@embroider/rollup-plugin-hbs": "0.42.3", + "@rollup/plugin-babel": "^5.3.0", + "fs-extra": "^7.0.0" + }, + "peerDependencies": { + "@embroider/core": "^0.39.1", + "vite": "^2.0.0" + }, + "devDependencies": { + "@embroider/core": "0.42.3", + "typescript": "~4.0.0", + "vite": "^2.3.8" + }, + "engines": { + "node": "12.* || 14.* || >= 16" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/vite/src/index.ts b/packages/vite/src/index.ts new file mode 100644 index 000000000..8a75a3969 --- /dev/null +++ b/packages/vite/src/index.ts @@ -0,0 +1,136 @@ +import { realpathSync } from 'fs'; +import { copyFile } from 'fs-extra'; +import { join } from 'path'; +import { + HTMLEntrypoint, + Packager, + PackagerConstructor, + Variant, + applyVariantToBabelConfig, + getAppMeta, + getPackagerCacheDir, +} from '@embroider/core'; +import { build } from 'vite'; +import templateCompilerPlugin from '@embroider/rollup-plugin-hbs'; +import { babel } from '@rollup/plugin-babel'; +import { AllowedViteConfig, Options } from './options'; + +const Vite: PackagerConstructor = class Vite implements Packager { + static annotation = '@embroider/vite'; + + private pathToVanillaApp: string; + private variant: Variant; + private viteConfig: AllowedViteConfig; + + constructor( + inputPath: string, + private outputPath: string, + variants: Variant[], + _consoleWrite: (msg: string) => void, + options?: Options + ) { + this.pathToVanillaApp = realpathSync(inputPath); + + // For now we're not worried about building each variant + // Let's just assume we have one + this.variant = variants[0]; + + this.viteConfig = options?.viteConfig ?? {}; + } + + async build(): Promise { + const meta = getAppMeta(this.pathToVanillaApp); + const entrypoints: HTMLEntrypoint[] = []; + const otherAssets: string[] = []; + const rootURL = meta['root-url']; + + for (let relativePath of meta.assets) { + if (/\.html/i.test(relativePath)) { + entrypoints.push(new HTMLEntrypoint(this.pathToVanillaApp, rootURL, '/', relativePath)); + } else { + otherAssets.push(relativePath); + } + } + + await build({ + // Options we want to override the defaults for, but users can override themselves, too + logLevel: 'error', + + // User options + ...this.viteConfig, + + // Options we *don't* want to allow users to override + base: meta['root-url'], + cacheDir: getPackagerCacheDir('vite'), + configFile: false, + mode: this.variant.optimizeForProduction ? 'production' : 'development', + resolve: { + ...this.viteConfig.resolve, + extensions: meta['resolvable-extensions'], + }, + root: this.pathToVanillaApp, + + build: { + ...this.viteConfig.build, + outDir: this.outputPath, + rollupOptions: { + ...this.viteConfig.build?.rollupOptions, + input: entrypoints.map(entry => join(this.pathToVanillaApp, entry.filename)), + }, + commonjsOptions: { + ...this.viteConfig.build?.commonjsOptions, + extensions: meta['resolvable-extensions'], + include: [/.*/], + }, + }, + + plugins: [ + templateCompilerPlugin({ + templateCompilerFile: join(this.pathToVanillaApp, meta['template-compiler'].filename), + variant: this.variant, + }), + + babel({ + // Embroider includes the Runtime plugin in the generated Babel config + babelHelpers: 'runtime', + + // Path to the Embroider-generated file defining a filtering function + // eslint-disable-next-line @typescript-eslint/no-require-imports + filter: require(join(this.pathToVanillaApp, meta.babel.fileFilter)), + + // Add the Babel config produced by Embroider + ...this.getBabelConfig(meta.babel.filename), + }), + + ...(this.viteConfig.plugins ?? []), + ], + }); + + await Promise.all([ + // Vite does not process non-module scripts, so we need to copy them over + ...entrypoints + .reduce((acc, entrypoint) => [...acc, ...entrypoint.scripts], [] as string[]) + .map(script => this.copyThrough(script)), + + // Copy over other assets + // This more-or-less mimics what Vite does for `public` files + ...otherAssets.map(relativePath => this.copyThrough(relativePath)), + ]); + } + + private copyThrough(path: string) { + const source = join(this.pathToVanillaApp, path); + const dest = join(this.outputPath, path); + + return copyFile(source, dest); + } + + private getBabelConfig(configFileName: string) { + const appBabelConfigPath = join(this.pathToVanillaApp, configFileName); + + // eslint-disable-next-line @typescript-eslint/no-require-imports + return applyVariantToBabelConfig(this.variant, require(appBabelConfigPath)); + } +}; + +export { Vite }; diff --git a/packages/vite/src/options.ts b/packages/vite/src/options.ts new file mode 100644 index 000000000..0b66cafea --- /dev/null +++ b/packages/vite/src/options.ts @@ -0,0 +1,26 @@ +import { BuildOptions, InlineConfig } from 'vite'; + +type CommonJSOptions = Required; +type ResolveOptions = Required; +type RollupOptions = Required; + +type AllowedCommonJSOptions = Omit; +type AllowedResolveOptions = Omit; +type AllowedRollupOptions = Omit; + +type AllowedBuildOptions = Omit & { + commonjsOptions?: AllowedCommonJSOptions; + rollupOptions?: AllowedRollupOptions; +}; + +export type AllowedViteConfig = Omit< + InlineConfig, + 'base' | 'build' | 'cacheDir' | 'configFile' | 'mode' | 'resolve' | 'root' +> & { + build?: AllowedBuildOptions; + resolve?: AllowedResolveOptions; +}; + +export interface Options { + viteConfig?: AllowedViteConfig; +} diff --git a/test-packages/support/index.ts b/test-packages/support/index.ts index 1de9de50e..01755b045 100644 --- a/test-packages/support/index.ts +++ b/test-packages/support/index.ts @@ -107,6 +107,22 @@ export function definesPattern(runtimeName: string, buildTimeName: string): RegE ); } +const PACKAGERS = ['@embroider/vite', '@embroider/webpack']; + +export function fetchPackagerFromDependencies(app: any) { + let Packager; + + for (const packagerName of PACKAGERS) { + if (app.project.pkg.devDependencies[packagerName]) { + Packager = require(packagerName); + } + } + + return { + Packager, + }; +} + export { Project } from './project'; export { default as BuildResult } from './build'; export { expectFilesAt, ExpectFile } from './file-assertions'; diff --git a/tests/app-template/ember-cli-build.js b/tests/app-template/ember-cli-build.js index 1d9b7e50e..37e2f1669 100644 --- a/tests/app-template/ember-cli-build.js +++ b/tests/app-template/ember-cli-build.js @@ -1,6 +1,7 @@ 'use strict'; const EmberApp = require('ember-cli/lib/broccoli/ember-app'); +const { fetchPackagerFromDependencies } = require('@embroider/test-support'); module.exports = function (defaults) { let app = new EmberApp(defaults, { @@ -20,8 +21,8 @@ module.exports = function (defaults) { // please specify an object with the list of modules as keys // along with the exports of each module as its value. - const { Webpack } = require('@embroider/webpack'); - return require('@embroider/compat').compatBuild(app, Webpack, { + const { Packager } = fetchPackagerFromDependencies(app); + return require('@embroider/compat').compatBuild(app, Packager, { skipBabel: [ { package: 'qunit', diff --git a/tests/app-template/package.json b/tests/app-template/package.json index c8e365066..9f7a87038 100644 --- a/tests/app-template/package.json +++ b/tests/app-template/package.json @@ -28,8 +28,8 @@ "@ember/test-helpers": "^2.2.5", "@embroider/compat": "0.42.3", "@embroider/core": "0.42.3", - "@embroider/webpack": "0.42.3", "@embroider/router": "0.42.3", + "@embroider/test-support": "0.36.0", "@glimmer/component": "^1.0.4", "@glimmer/tracking": "^1.0.4", "babel-eslint": "^10.1.0", diff --git a/tests/scenarios/package.json b/tests/scenarios/package.json index 7c78df2d5..a4de1246f 100644 --- a/tests/scenarios/package.json +++ b/tests/scenarios/package.json @@ -19,6 +19,8 @@ "license": "MIT", "devDependencies": { "@embroider/macros": "0.42.3", + "@embroider/vite": "0.42.3", + "@embroider/webpack": "0.42.3", "bootstrap": "^4.3.1", "broccoli-funnel": "^3.0.5", "broccoli-merge-trees": "^3.0.2", diff --git a/tests/scenarios/scenarios.ts b/tests/scenarios/scenarios.ts index 727193acd..291d1a982 100644 --- a/tests/scenarios/scenarios.ts +++ b/tests/scenarios/scenarios.ts @@ -35,13 +35,26 @@ async function release(project: Project) { project.linkDevDependency('ember-cli', { baseDir: __dirname, resolveName: 'ember-cli-latest' }); } +async function packager_vite(project: Project) { + project.linkDevDependency('@embroider/vite', { baseDir: __dirname }); +} + +async function packager_webpack(project: Project) { + project.linkDevDependency('@embroider/webpack', { baseDir: __dirname }); +} + export function supportMatrix(scenarios: Scenarios) { - return scenarios.expand({ - lts_3_16, - lts_3_20, - lts_3_24, - release, - }); + return scenarios + .expand({ + lts_3_16, + lts_3_20, + lts_3_24, + release, + }) + .expand({ + packager_vite, + packager_webpack, + }); } export const appScenarios = supportMatrix(Scenarios.fromDir(dirname(require.resolve('../app-template/package.json')))); diff --git a/yarn.lock b/yarn.lock index 4b5f9dec9..62e1c53d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -147,7 +147,7 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.14.5", "@babel/helper-module-imports@^7.8.3": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.14.5", "@babel/helper-module-imports@^7.8.3": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3" integrity sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ== @@ -1890,6 +1890,23 @@ dependencies: "@octokit/openapi-types" "^7.2.3" +"@rollup/plugin-babel@^5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879" + integrity sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@rollup/pluginutils" "^3.1.0" + +"@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + "@simple-dom/document@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@simple-dom/document/-/document-1.4.0.tgz#af60855f957f284d436983798ef1006cca1a1678" @@ -2142,6 +2159,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.48.tgz#18dc8091b285df90db2f25aa7d906cfc394b7f74" integrity sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew== +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + "@types/estree@^0.0.47": version "0.0.47" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" @@ -9283,6 +9305,11 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +esbuild@^0.12.8: + version "0.12.13" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.13.tgz#666e1e6c50dbde40c02824cc60296574ebd588c5" + integrity sha512-fnKinmXcW1DMYnf1Ol3ZO0yU7dBDCuPcE4XDwceIy2zqTvB4G0NfonqgdvPMJi/IXRoVi/w9r9O5xxg/SqiAxA== + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -9706,6 +9733,11 @@ estree-walker@^0.6.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -10643,7 +10675,7 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.3.1: +fsevents@~2.3.1, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -14822,7 +14854,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== @@ -14970,6 +15002,15 @@ postcss@^8.2.15: nanoid "^3.1.23" source-map-js "^0.6.2" +postcss@^8.3.4: + version "8.3.5" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709" + integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== + dependencies: + colorette "^1.2.2" + nanoid "^3.1.23" + source-map-js "^0.6.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -15982,6 +16023,13 @@ rollup@^1.12.0: "@types/node" "*" acorn "^7.1.0" +rollup@^2.38.5, rollup@^2.52.6: + version "2.52.6" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.52.6.tgz#7c7546d170dead0e7db0b6c709f7f34398498a8e" + integrity sha512-H+Xudmwf8KO+xji8njQNoIQRp8l+iQge/NdUR20JngTxVYdEEnlpkMvQ71YGLl3+xZcPecmdj4q2lrClKaPdRA== + optionalDependencies: + fsevents "~2.3.2" + rsvp@^3.0.14, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.1.0: version "3.6.2" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" @@ -18006,6 +18054,18 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.3.8.tgz#42e3e03953859fd410e4e6ab3d1cca0aab2adc3c" + integrity sha512-QiEx+iqNnJntSgSF2fWRQvRey9pORIrtNJzNyBJXwc+BdzWs83FQolX84cTBo393cfhObrtWa6180dAa4NLDiQ== + dependencies: + esbuild "^0.12.8" + postcss "^8.3.4" + resolve "^1.20.0" + rollup "^2.38.5" + optionalDependencies: + fsevents "~2.3.2" + vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"