Skip to content

Commit

Permalink
Adding vite vocab plugin package (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
DanDroryAu authored Nov 11, 2024
1 parent 3a3ab6f commit 3af37f0
Show file tree
Hide file tree
Showing 30 changed files with 791 additions and 31 deletions.
94 changes: 93 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ Vocab helps you ship multiple languages without compromising the reliability of
- **Strongly typed with TypeScript**\
When using translations TypeScript will ensure code only accesses valid translations and translations are passed all required dynamic values.

## Table of contents

- [Getting started](#getting-started)
- [Step 1: Install Dependencies](#step-1-install-dependencies)
- [Step 2: Configure Vocab](#step-2-configure-vocab)
- [Step 3: Set the language using the React Provider](#step-3-set-the-language-using-the-react-provider)
- [Step 4: Create translations](#step-4-create-translations)
- [Step 5: Compile and consume translations](#step-5-compile-and-consume-translations)
- [Step 6: [Optional] Set up plugin](#step-6-optional-set-up-plugin)
- [Step 7: [Optional] Optimize for fast page loading](#step-7-optional-optimize-for-fast-page-loading)

## Getting started

### Step 1: Install Dependencies
Expand Down Expand Up @@ -159,7 +170,9 @@ function MyComponent({ children }) {
}
```

### Step 6: [Optional] Set up Webpack plugin
### Step 6: [Optional] Set up plugin

#### Webpack Plugin

With the default setup, every language is loaded into your web application all the time, potentially leading to a large bundle size.
Ideally you will want to switch out the Node.js/default runtime for the web runtime, which only loads the active language.
Expand All @@ -181,6 +194,85 @@ module.exports = {
};
```

#### Vite Plugin _(this plugin is experimental)_

> [!NOTE]
> This plugin is still experimental and may not work in all cases. If you encounter any issues, please open an issue on the Vocab GitHub repository.
Vocab also provides a Vite plugin to handle the same functionality as the Webpack plugin.

```shell
npm i --save-dev @vocab/vite
```

default usage

```js
// vite.config.js
import { defineConfig } from 'vite';
import { vocabPluginVite } from '@vocab/vite';
import vocabConfig from './vocab.config.cjs';

export default defineConfig({
plugins: [
vocabPluginVite({
vocabConfig
})
]
});
```

#### createVocabChunks

If you want to combine all language files into a single chunk, you can use the `createVocabChunks` function.
Simply use the function in your `manualChunks` configuration.

```js
// vite.config.js
import { defineConfig } from 'vite';
import { vocabPluginVite } from '@vocab/vite';
import { createVocabChunks } from '@vocab/vite/create-vocab-chunks';
import vocabConfig from './vocab.config.cjs';

export default defineConfig({
plugins: [
vocabPluginVite({
vocabConfig
})
],
build: {
rollupOptions: {
output: {
manualChunks: (id, ctx) => {
// handle your own manual chunks before or after the vocab chunks.
const languageChunkName = createVocabChunks(
id,
ctx
);
if (languageChunkName) {
// vocab has found a language chunk. Either return it or handle it in your own way.
return languageChunkName;
}
}
}
}
}
});
```

#### VocabPluginOptions

```ts
type VocabPluginOptions = {
/**
* The Vocab configuration file.
* The type can be found in the `@vocab/core/types`.
* This value is required
*/
vocabConfig: UserConfig;
};
```

### Step 7: [Optional] Optimize for fast page loading

Using the above method without optimizing what chunks webpack uses you may find the page needing to do an extra round trip to load languages on a page.
Expand Down
6 changes: 0 additions & 6 deletions fixtures/simple-vite/vite.config.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vocab simple-vite plugin test</title>
<title>vocab vite plugin test</title>
</head>
<body>
<div id="root"></div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "@vocab-fixtures/simple-vite",
"version": "1.0.0",
"name": "@vocab-fixtures/vite",
"version": "0.0.1",
"author": "SEEK",
"private": true,
"scripts": {
"compile": "vocab compile",
"start": "vocab compile --watch & vite",
"build": "vocab compile && vite build"
"build": "vocab compile && vite build",
"preview": "vocab compile && vite preview"
},
"dependencies": {
"@babel/core": "^7.12.0",
Expand All @@ -16,6 +17,7 @@
"@vocab/core": "workspace:*",
"@vocab/pseudo-localize": "workspace:*",
"@vocab/react": "workspace:*",
"@vocab/vite": "workspace:*",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"vite": "^5.4.8"
Expand Down
File renamed without changes.
File renamed without changes.
25 changes: 25 additions & 0 deletions fixtures/vite/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig } from 'vite';
import vitePluginVocab from '@vocab/vite';
import { createVocabChunks } from '@vocab/vite/create-vocab-chunks';
import vocabConfig from './vocab.config.cjs';

export default defineConfig({
plugins: [
vitePluginVocab({
vocabConfig,
}),
],
build: {
rollupOptions: {
output: {
chunkFileNames: '[name].js',
manualChunks: (id, ctx) => {
const vocabChunk = createVocabChunks(id, ctx);
if (vocabChunk) {
return vocabChunk;
}
},
},
},
},
});
File renamed without changes.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"start:direct": "pnpm dev && pnpm --filter @vocab-fixtures/direct compile && pnpm start-fixture direct",
"start:server": "pnpm dev && pnpm --filter @vocab-fixtures/server compile && pnpm start-fixture server",
"start:simple": "pnpm dev && pnpm --filter @vocab-fixtures/simple compile && pnpm start-fixture simple",
"start:vite": "pnpm dev && pnpm --filter @vocab-fixtures/vite compile && pnpm preview-vite-fixture vite",
"build": "preconstruct build",
"dev": "preconstruct dev",
"watch": "preconstruct watch",
Expand All @@ -32,6 +33,7 @@
"version": "changeset version && pnpm install --lockfile-only",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"copy-readme-to-packages": "tsx scripts/copy-readme-to-packages",
"preview-vite-fixture": "tsx test-helpers/src/preview-vite-fixture",
"start-fixture": "tsx test-helpers/src/start-fixture",
"run-server-fixture": "tsx test-helpers/src/run-server-fixture",
"compile-fixtures": "pnpm --filter @vocab-fixtures/* compile",
Expand Down
4 changes: 4 additions & 0 deletions packages/vite/create-language/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "dist/vocab-vite-create-language.cjs.js",
"module": "dist/vocab-vite-create-language.esm.js"
}
4 changes: 4 additions & 0 deletions packages/vite/create-vocab-chunks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "dist/vocab-vite-create-vocab-chunks.cjs.js",
"module": "dist/vocab-vite-create-vocab-chunks.esm.js"
}
55 changes: 55 additions & 0 deletions packages/vite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "@vocab/vite",
"version": "0.0.1",
"author": "SEEK",
"license": "MIT",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/seek-oss/vocab.git",
"directory": "packages/vite"
},
"main": "dist/vocab-vite.cjs.js",
"module": "dist/vocab-vite.esm.js",
"exports": {
"./package.json": "./package.json",
".": {
"module": "./dist/vocab-vite.esm.js",
"default": "./dist/vocab-vite.cjs.js"
},
"./create-language": {
"module": "./create-language/dist/vocab-vite-create-language.esm.js",
"default": "./create-language/dist/vocab-vite-create-language.cjs.js"
},
"./create-vocab-chunks": {
"module": "./create-vocab-chunks/dist/vocab-vite-create-vocab-chunks.esm.js",
"default": "./create-vocab-chunks/dist/vocab-vite-create-vocab-chunks.cjs.js"
}
},
"preconstruct": {
"entrypoints": [
"index.ts",
"create-language.ts",
"create-vocab-chunks.ts"
]
},
"files": [
"dist",
"create-language",
"create-vocab-chunks"
],
"dependencies": {
"@vocab/core": "workspace:^",
"cjs-module-lexer": "^1.2.2",
"debug": "^4.3.1",
"es-module-lexer": "^1.0.0",
"picocolors": "^1.0.0"
},
"devDependencies": {
"@types/debug": "^4.1.5",
"vite": "^5.4.8"
},
"peerDependencies": {
"vite": "^5.4.8"
}
}
3 changes: 3 additions & 0 deletions packages/vite/src/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const compiledVocabFileFilter = /\.vocab[\\/]index\.(?:ts|js|cjs|mjs)$/;
export const virtualModuleId = 'virtual:vocab';
export const sourceQueryKey = '?source=';
29 changes: 29 additions & 0 deletions packages/vite/src/create-language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { TranslationModule } from '@vocab/core';
import { getParsedICUMessages } from '@vocab/core/icu-handler';

export { createTranslationFile } from '@vocab/core/translation-file';

export const createLanguage = (
loadImport: () => Promise<any>,
): TranslationModule<any> => {
let promiseValue: Promise<any>;
let resolvedValue: any;

return {
getValue: (locale) => {
if (!resolvedValue) {
return undefined;
}
return getParsedICUMessages(resolvedValue, locale);
},
load: () => {
if (!promiseValue) {
promiseValue = loadImport();
promiseValue.then((value) => {
resolvedValue = value.default;
});
}
return promiseValue;
},
};
};
45 changes: 45 additions & 0 deletions packages/vite/src/create-vocab-chunks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Rollup } from 'vite';
import { trace as _trace } from './logger';

const trace = _trace.extend('create-vocab-chunks');

export const createVocabChunks = (
id: string,
{ getModuleInfo }: Rollup.ManualChunkMeta,
) => {
const match = /(\w+)-virtual:vocab/.exec(id);
if (!match) {
return;
}

const language = match[1];
const dependentEntryPoints: string[] = [];

const rootModuleInfo = getModuleInfo(id);

if (!rootModuleInfo) {
trace(`No module info found for ${id}`);
}

const idsToHandle = new Set<string>(getModuleInfo(id)?.dynamicImporters);

for (const moduleId of idsToHandle) {
const moduleInfo = getModuleInfo(moduleId);
if (!moduleInfo) {
trace(`No module info found for ${moduleId}`);
continue;
}

const { isEntry, dynamicImporters, importers } = moduleInfo;

if (isEntry || dynamicImporters.length > 0) {
dependentEntryPoints.push(moduleId);
}

for (const importerId of importers) idsToHandle.add(importerId);
}

if (dependentEntryPoints.length > 0) {
return `${language}-translations`;
}
};
45 changes: 45 additions & 0 deletions packages/vite/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Plugin as VitePlugin } from 'vite';
import type { UserConfig } from '@vocab/core';

import { transformVocabFile } from './transform-vocab-file';
import { virtualResourceLoader } from './virtual-resource-loader';

import { trace } from './logger';

import { compiledVocabFileFilter, virtualModuleId } from './consts';

export type VocabPluginOptions = {
vocabConfig: UserConfig;
};

export default function vitePluginVocab({
vocabConfig,
}: VocabPluginOptions): VitePlugin {
trace(
`Creating Vocab plugin${
vocabConfig ? ` with config file ${vocabConfig}` : ''
}`,
);
return {
name: 'vite-plugin-vocab',
resolveId(id) {
if (id.includes(virtualModuleId)) {
return `\0${id}`;
}
},
load(id) {
if (id.includes(virtualModuleId)) {
return virtualResourceLoader(id);
}
},
async transform(code, id) {
if (compiledVocabFileFilter.test(id)) {
const transformedCode = await transformVocabFile(code, id, vocabConfig);
return {
code: transformedCode,
map: null, // provide source map if available
};
}
},
};
}
9 changes: 9 additions & 0 deletions packages/vite/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import pc from 'picocolors';
import debug from 'debug';

export const trace = debug(`vocab:vite`);

export const log = (...params: unknown[]) => {
// eslint-disable-next-line no-console
console.log(pc.yellow('Vocab'), ...params);
};
Loading

0 comments on commit 3af37f0

Please sign in to comment.