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

feat(vite-plugin-angular): add support for JIT mode #374

Merged
merged 8 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 2 additions & 8 deletions apps/analog-app/src/app/routes/shipping/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,8 @@ import { CartService } from '../../cart.service';
selector: 'app-shipping',
standalone: true,
imports: [NgForOf, CurrencyPipe, AsyncPipe],
template: `
<h3>Shipping Prices</h3>

<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
<span>{{ shipping.type }}</span>
<span>{{ shipping.price | currency }}</span>
</div>
`,
templateUrl: './shipping.html',
styleUrls: ['./shipping.scss'],
})
export default class ShippingComponent implements OnInit {
private cartService = inject(CartService);
Expand Down
6 changes: 6 additions & 0 deletions apps/analog-app/src/app/routes/shipping/shipping.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<h3>Shipping Prices</h3>

<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
<span>{{ shipping.type }}</span>
<span>{{ shipping.price | currency }}</span>
</div>
3 changes: 3 additions & 0 deletions apps/analog-app/src/app/routes/shipping/shipping.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
h3 {
border: solid 1px darkblue;
}
7 changes: 7 additions & 0 deletions libs/top-bar/src/lib/top-bar/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<a routerLink="/">
<h1>My Store</h1>
</a>

<a routerLink="/cart" class="button fancy-button">
<i class="material-icons">shopping_cart</i>Checkout
</a>
14 changes: 5 additions & 9 deletions libs/top-bar/src/lib/top-bar/top-bar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@ import {
Component,
ViewEncapsulation,
} from '@angular/core';
import { RouterLinkWithHref } from '@angular/router';
import { Router, RouterLinkWithHref } from '@angular/router';

@Component({
selector: 'analogjs-top-bar',
standalone: true,
imports: [RouterLinkWithHref],
template: ` <a routerLink="/">
<h1>My Store</h1>
</a>

<a routerLink="/cart" class="button fancy-button">
<i class="material-icons">shopping_cart</i>Checkout
</a>`,
templateUrl: './template.html',
encapsulation: ViewEncapsulation.Emulated,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TopBarComponent {}
export class TopBarComponent {
constructor(router: Router) {}
}
1 change: 0 additions & 1 deletion libs/top-bar/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["node", "vitest/globals"]
},
"files": ["src/test-setup.ts"],
Expand Down
6 changes: 6 additions & 0 deletions libs/top-bar/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

import { defineConfig } from 'vite';
import { offsetFromRoot } from '@nx/devkit';
import angular from '@analogjs/vite-plugin-angular';

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
return {
root: 'src',
plugins: [
angular({
tsconfig: 'libs/top-bar/tsconfig.spec.json',
}),
],
test: {
globals: true,
environment: 'jsdom',
Expand Down
3 changes: 3 additions & 0 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"test": {
"dependsOn": ["^build"]
},
"serve": {
"dependsOn": ["^build"]
},
Expand Down
1 change: 1 addition & 0 deletions packages/platform/src/lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ export interface Options {
vite?: PluginOptions;
nitro?: NitroConfig;
apiPrefix?: string;
jit?: boolean;
}
2 changes: 1 addition & 1 deletion packages/platform/src/lib/platform-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ export function platformPlugin(opts: Options = {}): Plugin[] {
(platformOptions.ssr
? devServerPlugin({ entryServer: opts.entryServer })
: false) as Plugin,
...angular(opts?.vite),
...angular({ jit: platformOptions.jit, ...(opts?.vite ?? {}) }),
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Plugin } from 'vite';
import { requiresLinking } from '@angular-devkit/build-angular/src/babel/webpack-loader';
import { loadEsmModule } from '@angular-devkit/build-angular/src/utils/load-esm';
import { transformAsync } from '@babel/core';
import angularApplicationPreset from '@angular-devkit/build-angular/src/babel/presets/application';

export function buildOptimizerPlugin({ isProd }: { isProd: boolean }): Plugin {
return {
name: '@analogjs/vite-plugin-angular-optimizer',
apply: 'build',
config() {
return {
esbuild: {
legalComments: 'none',
keepNames: false,
define: isProd
? {
ngDevMode: 'false',
ngJitMode: 'false',
ngI18nClosureMode: 'false',
}
: undefined,
supported: {
// Native async/await is not supported with Zone.js. Disabling support here will cause
// esbuild to downlevel async/await to a Zone.js supported form.
'async-await': false,
// Zone.js also does not support async generators or async iterators. However, esbuild does
// not currently support downleveling either of them. Instead babel is used within the JS/TS
// loader to perform the downlevel transformation. They are both disabled here to allow
// esbuild to handle them in the future if support is ever added.
// NOTE: If esbuild adds support in the future, the babel support for these can be disabled.
'async-generator': false,
'for-await': false,
},
},
};
},
async transform(code, id) {
if (/\.[cm]?js$/.test(id)) {
const angularPackage = /[\\/]node_modules[\\/]@angular[\\/]/.test(id);

const linkerPluginCreator = (
await loadEsmModule<
typeof import('@angular/compiler-cli/linker/babel')
>('@angular/compiler-cli/linker/babel')
).createEs2015LinkerPlugin;

const forceAsyncTransformation =
!/[\\/][_f]?esm2015[\\/]/.test(id) &&
/for\s+await\s*\(|async\s+function\s*\*/.test(code);
const shouldLink = await requiresLinking(id, code);
const useInputSourcemap = (!isProd ? undefined : false) as undefined;

if (!forceAsyncTransformation && !isProd && !shouldLink) {
return {
code: isProd
? code.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '')
: code,
};
}

const result = await transformAsync(code, {
filename: id,
inputSourceMap: useInputSourcemap,
sourceMaps: !isProd ? 'inline' : false,
compact: false,
configFile: false,
babelrc: false,
browserslistConfigFile: false,
plugins: [],
presets: [
[
angularApplicationPreset,
{
angularLinker: {
shouldLink,
jitMode: false,
linkerPluginCreator,
},
forceAsyncTransformation,
optimize: isProd && {
looseEnums: angularPackage,
pureTopLevel: angularPackage,
},
},
],
],
});

return {
code: result?.code || '',
Copy link
Contributor

Choose a reason for hiding this comment

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

minor (non-blocking): Wouldn't make sense to use the ?? instead of || here?

map: result?.map as any,
};
}

return;
},
};
}
69 changes: 69 additions & 0 deletions packages/vite-plugin-angular/src/lib/angular-jit-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Plugin, PluginContainer, ViteDevServer } from 'vite';
import { readFileSync } from 'fs';

export function jitPlugin({
inlineStylesExtension,
}: {
inlineStylesExtension: string;
}): Plugin {
let styleTransform: PluginContainer['transform'] | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: if we have a utils library/functions somewhere we could add a Utility type like this:
type ExtractMethodSignature<T extends Record<any, any>, K> = T[K];

and this way you can change this to:
let styleTransform: ExtractMethodSignature<PluginContainer, 'transform'> | undefined;

This would make only sense if we do this many times in our code. Maybe there is a better TS solution for this.

Copy link

@marcus-sa marcus-sa May 9, 2023

Choose a reason for hiding this comment

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

@gergobergo the correct TS solution is already being used. There's no point in abstracting something that doesn't need to be abstracted.

let watchMode = false;
let viteServer: ViteDevServer | undefined;
let cssPlugin: Plugin | undefined;

return {
name: '@analogjs/vite-plugin-angular-jit',
config(_config, { command }) {
watchMode = command === 'serve';
},
buildStart({ plugins }) {
if (Array.isArray(plugins)) {
cssPlugin = plugins.find((plugin) => plugin.name === 'vite:css');
}

styleTransform = watchMode
? viteServer!.pluginContainer.transform
: (cssPlugin!.transform as PluginContainer['transform']);
},
configureServer(server) {
viteServer = server;
},
resolveId(id: string) {
if (id.startsWith('virtual:angular')) {
return `\0${id}`;
}

return;
},
async load(id: string) {
if (id.includes('virtual:angular:jit:template:file;')) {
const contents = readFileSync(id.split('file;')[1], 'utf-8');

return `export default \`${contents}\`;`;
} else if (id.includes('virtual:angular:jit:style:inline;')) {
const styleId = id.split('style:inline;')[1];

const decodedStyles = Buffer.from(
decodeURIComponent(styleId),
'base64'
).toString();

let styles: string | undefined = '';

try {
const compiled = await styleTransform!(
decodedStyles,
`${styleId}.${inlineStylesExtension}?direct`
);
styles = compiled?.code;
} catch (e) {
console.error(`${e}`);
}

return `export default \`${styles}\``;
}

return;
},
};
}
Loading