Skip to content

Commit

Permalink
feat!: default enable for JIT compilation (#1852)
Browse files Browse the repository at this point in the history
  • Loading branch information
kazupon committed May 14, 2024
1 parent 51a009d commit 8b77ee3
Show file tree
Hide file tree
Showing 35 changed files with 81 additions and 197 deletions.
36 changes: 21 additions & 15 deletions docs/guide/advanced/optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,33 @@

## Performance

As described in "[installation](../installation##from-cdn-or-without-a-bundler)" section, Vue I18n offer the following two built ES modules for Bundler.
As described in "[Different Distribution files](../extra/dist##from-cdn-or-without-a-bundler)" section, Vue I18n offer the following two built ES modules for Bundler.

- message compiler + runtime: **`vue-i18n.esm-bundler.js`**
- runtime only: **`vue-i18n.runtime.esm-bundler.js`**

For bundler, it’s configured to bundle `vue-i18n.esm-bundler.js` with [`@intlify/bundle-tools`](https://github.com/intlify/bundle-tools#intlifybundle-tools) as default. If you want to reduce the bundle size further, you can configure the bundler to use `vue-i18n.runtime.esm-bundler.js`, which is runtime only.
For bundler, it’s configured to bundle `vue-i18n.esm-bundler.js` with [`@intlify/unplugin-vue-i18n`](https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n) as default. If you want to reduce the bundle size further, you can configure the bundler to use `vue-i18n.runtime.esm-bundler.js`, which is runtime only.

:::danger NOTE
IF [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) is enabled, `vue-i18n.esm-bundler.js` would not work with compiler due to `eval` statements. These statements violate the `default-src 'self'` header. Instead you need to use `vue-i18n.runtime.esm-bundler.js`.
:::
The use of ES Module `vue-i18n.runtime.esm-bundler.js` means that **all locale messages have to pre-compile to Message functions or AST resources**. what this means it improves performance because vue-i18n just only execute Message functions, so no compilation.

:::warning NOTICE
From v9.3, the CSP issue can be worked around by JIT compilation of the vue-i18n message compiler. See [JIT compilation for details](#jit-compilation).
:::tip NOTE
Before v9.3, the locale messages will be compiled to Message functions, after v9.3 or later these will be compiled to AST with `@intlify/bundle-tools`.
:::

The use of this ES Module means that **all locale messages have to pre-compile to Message functions**. what this means it improves performance because vue-i18n just only execute Message functions, so no compilation.

Also, the message compiler is not bundled, therefore **bundle size can be reduced**
:::tip NOTE
Before v9.3, all locale messages are compiled with `@intlify/unplugin-vue-i18n`, so the message compiler is not bundled, **bundle size can be reduced**.

:::warning NOTICE
If you are using the JIT compilation, all locale messages will not necessarily be compiled with the Message function.
After v9.3, since the message compiler is also bundled, the bundle size cannot be reduced. **This is a trade-off**.
About the reason, See [JIT compilation for details](#jit-compilation).
:::

Also, since the message compiler is also bundled, the bundle size cannot be reduced. **This is a trade-off**.
:::danger NOTE
If [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) is enabled in before v9.3, `vue-i18n.esm-bundler.js` would not work with compiler due to `eval` statements. These statements violate the `default-src 'self'` header. Instead you need to use `vue-i18n.runtime.esm-bundler.js`.
:::

:::warning NOTICE
From v9.3, the CSP issue can be worked around by JIT compilation of the vue-i18n message compiler. See [JIT compilation for details](#jit-compilation).
:::

## How to configure

Expand Down Expand Up @@ -132,14 +134,14 @@ About how to configure for bundler, see the [here](#configure-feature-flags-for-
:new: 9.3+
:::
Before v9.3, vue-i18n message compiler precompiled locale messages like AOT.
Before v9.3, vue-i18n message compiler precompiled locale messages like AOT (Ahead Of Time).
However, it had the following issues:
- CSP issues: hard to work on service/web workers, edge-side runtimes of CDNs and etc.
- Back-end integration: hard to get messages from back-end such as database via API and localize them dynamically
To solve these issues, JIT style compilation is supported message compiler.
To solve these issues, JIT (Just In Time) style compilation is supported message compiler.
Each time localization is performed in an application using `$t` or `t` functions, message resources will be compiled on message compiler.
Expand All @@ -152,6 +154,10 @@ You need to configure the following feature flag with `esm-bundler` build and bu
This feature is opted out as default, because compatibility with previous version before v9.3.
:::
:::warning NOTICE
From v10, JIT compilation is enabled by default, so it is no longer necessary to set the `__INTLIFY_JIT_COMPILATION__` flag in the bundler.
:::
About how to configure for bundler, see the [here](#configure-feature-flags-for-bundler).
Expand Down
16 changes: 16 additions & 0 deletions docs/guide/migration/breaking10.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
Vue I18n v10 **is still an alpha version**.
:::

## Default enable for JIT compilation

**Reason**: CSP problems can be solved and dynamic resources can be supported

JIT compilation was introduced in v9.3. It was not enabled by default.

Nuxt I18n, which integrates vue-i18n, already has this feature enabled and stable by default.
https://i18n.nuxtjs.org/docs/options/compilation#jit

To use this feature in Vue I18n, we had to use bundler and `@intlify/unplugin-vue-i18n` to enable the `__INTLIFY_JIT_COMPILATION__` flag.
By default in the JIT compilation, this flag is no longer needed starting with v10.

If you would not still using the JIT compilation and would be moving up to v10 or later, **you will need to rebuild your application once**.

About JIT compilation details, See "[Optimazation](../advanced/optimization.md)".

## Change `$t` and `t` overloaded signature for Legacy API mode

In Vue I18n v9, it has a different interface from the Composition API mode and Legacy API mode of `$t` and `t` overloaded signature.
Expand Down
50 changes: 1 addition & 49 deletions packages/core-base/src/compilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
detectHtmlTag
} from '@intlify/message-compiler'
import { format as formatMessage } from './format'
import { CoreErrorCodes, createCoreError } from './errors'

import type {
CompileOptions,
Expand Down Expand Up @@ -53,53 +52,6 @@ function baseCompile(
}

/* #__NO_SIDE_EFFECTS__ */
export const compileToFunction = <
Message = string,
MessageSource = string | ResourceNode
>(
message: MessageSource,
context: MessageCompilerContext
): MessageFunction<Message> => {
if (!isString(message)) {
throw createCoreError(CoreErrorCodes.NOT_SUPPORT_NON_STRING_MESSAGE)
}

if (__RUNTIME__) {
__DEV__ &&
warn(
`Runtime compilation is not supported in ${
__BUNDLE_FILENAME__ || 'N/A'
}.`
)
return (() => message) as MessageFunction<Message>
} else {
// check HTML message
const warnHtmlMessage = isBoolean(context.warnHtmlMessage)
? context.warnHtmlMessage
: true
__DEV__ && checkHtmlMessage(message, warnHtmlMessage)

// check caches
const onCacheKey = context.onCacheKey || defaultOnCacheKey
const cacheKey = onCacheKey(message)
const cached = (compileCache as MessageFunctions<Message>)[cacheKey]
if (cached) {
return cached
}

// compile
const { code, detectError } = baseCompile(message, context)

// evaluate function
const msg = new Function(`return ${code}`)() as MessageFunction<Message>

// if occurred compile error, don't cache
return !detectError
? ((compileCache as MessageFunctions<Message>)[cacheKey] = msg)
: msg
}
}

export function compile<
Message = string,
MessageSource = string | ResourceNode
Expand All @@ -111,7 +63,7 @@ export function compile<
(__ESM_BROWSER__ ||
__NODE_JS__ ||
__GLOBAL__ ||
(__FEATURE_JIT_COMPILATION__ && !__FEATURE_DROP_MESSAGE_COMPILER__)) &&
!__FEATURE_DROP_MESSAGE_COMPILER__) &&
isString(message)
) {
// check HTML message
Expand Down
4 changes: 0 additions & 4 deletions packages/core-base/src/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ export function initFeatureFlags(): void {
getGlobalThis().__INTLIFY_PROD_DEVTOOLS__ = false
}

if (typeof __FEATURE_JIT_COMPILATION__ !== 'boolean') {
getGlobalThis().__INTLIFY_JIT_COMPILATION__ = false
}

if (typeof __FEATURE_DROP_MESSAGE_COMPILER__ !== 'boolean') {
getGlobalThis().__INTLIFY_DROP_MESSAGE_COMPILER__ = false
}
Expand Down
26 changes: 1 addition & 25 deletions packages/core-base/test/compilation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@ vi.mock('@intlify/shared', async () => {
})

import { baseCompile } from '@intlify/message-compiler'
import {
compileToFunction,
compile,
isMessageAST,
clearCompileCache
} from '../src/compilation'
import { compile, isMessageAST, clearCompileCache } from '../src/compilation'
import { createMessageContext as context } from '../src/runtime'

const DEFAULT_CONTEXT = { locale: 'en', key: 'key' }
Expand Down Expand Up @@ -43,25 +38,6 @@ describe('isMessageAST', () => {
})
})

describe('compileToFunction', () => {
test('basic', () => {
const msg = compileToFunction('hello {name}!', DEFAULT_CONTEXT)
const ctx = context({
named: { name: 'kazupon' }
})
expect(msg(ctx)).toBe('hello kazupon!')
})

test('error', () => {
let occured = false
compileToFunction('hello {name!', {
...DEFAULT_CONTEXT,
onError: () => (occured = true)
})
expect(occured).toBe(true)
})
})

describe('compile', () => {
test('basic', () => {
const msg = compile('hello {name}!', DEFAULT_CONTEXT)
Expand Down
4 changes: 2 additions & 2 deletions packages/core-base/test/datetime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
registerMessageCompiler,
registerLocaleFallbacker
} from '../src/context'
import { compileToFunction } from '../src/compilation'
import { compile } from '../src/compilation'
import { fallbackWithLocaleChain } from '../src/fallbacker'

import type { DateTimeFormats } from '../src/types'
Expand Down Expand Up @@ -86,7 +86,7 @@ const dts = [
]

beforeEach(() => {
registerMessageCompiler(compileToFunction)
registerMessageCompiler(compile)
registerLocaleFallbacker(fallbackWithLocaleChain)
})

Expand Down
6 changes: 3 additions & 3 deletions packages/core-base/test/devtools.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createEmitter } from '@intlify/shared'
import { createCoreContext, translate } from '../src/index'
import { compileToFunction } from '../src/compilation'
import { compile } from '../src/compilation'
import { setDevToolsHook, getDevToolsHook } from '../src/devtools'

import type {
Expand Down Expand Up @@ -51,7 +51,7 @@ describe('translateDevTools', () => {
const HELLO = 'Hello {name}!'
const ctx = createCoreContext({
locale: 'en',
messageCompiler: compileToFunction,
messageCompiler: compile,
messages: {
en: {
hello: HELLO
Expand All @@ -78,7 +78,7 @@ describe('translateDevTools', () => {
const ctx = createCoreContext({
locale: 'en',
fallbackLocale: ['ja'],
messageCompiler: compileToFunction,
messageCompiler: compile,
messages: {
ja: {
hello: HELLO
Expand Down
4 changes: 2 additions & 2 deletions packages/core-base/test/number.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
registerMessageCompiler,
registerLocaleFallbacker
} from '../src/context'
import { compileToFunction } from '../src/compilation'
import { compile } from '../src/compilation'
import { fallbackWithLocaleChain } from '../src/fallbacker'
import { NumberFormats } from '../src/types/index'

Expand Down Expand Up @@ -69,7 +69,7 @@ const numberFormats: NumberFormats<MyNumberSchema, 'en-US' | 'ja-JP'> = {
}

beforeEach(() => {
registerMessageCompiler(compileToFunction)
registerMessageCompiler(compile)
registerLocaleFallbacker(fallbackWithLocaleChain)
})

Expand Down
6 changes: 2 additions & 4 deletions packages/core-base/test/translate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
registerMessageResolver,
registerLocaleFallbacker
} from '../src/context'
import { compileToFunction, compile } from '../src/compilation'
import { compile } from '../src/compilation'
import { fallbackWithLocaleChain } from '../src/fallbacker'
import { resolveValue } from '../src/resolver'
import { createTextNode } from './helper'
Expand All @@ -31,7 +31,7 @@ import type { MessageType, MessageProcessor } from '../src/runtime'
import type { PickupKeys } from '../src/types/utils'

beforeEach(() => {
registerMessageCompiler(compileToFunction)
registerMessageCompiler(compile)
registerMessageResolver(resolveValue)
registerLocaleFallbacker(fallbackWithLocaleChain)
})
Expand Down Expand Up @@ -978,8 +978,6 @@ describe('processor', () => {

describe('AST passing', () => {
test('simple text', () => {
registerMessageCompiler(compile)

const msg = 'hi kazupon !'
const { ast } = baseCompile(msg, { jit: true, location: false })

Expand Down
17 changes: 1 addition & 16 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
import {
registerMessageCompiler,
compileToFunction,
compile,
registerMessageResolver,
resolveValue,
registerLocaleFallbacker,
fallbackWithLocaleChain
} from '@intlify/core-base'
import { initFeatureFlags } from '../../core-base/src/misc'
import { getGlobalThis } from '@intlify/shared'

if (__ESM_BUNDLER__ && !__TEST__) {
initFeatureFlags()
if (__NODE_JS__) {
// avoid Node.js CSP for Function()
getGlobalThis().__INTLIFY_JIT_COMPILATION__ = true
}
}

// register message compiler at @intlify/core
if (
__ESM_BROWSER__ ||
__NODE_JS__ ||
__GLOBAL__ ||
__FEATURE_JIT_COMPILATION__
) {
registerMessageCompiler(compile)
} else {
registerMessageCompiler(compileToFunction)
}
registerMessageCompiler(compile)

// register message resolver at @intlify/core
registerMessageResolver(resolveValue)
Expand Down
4 changes: 1 addition & 3 deletions packages/core/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ if (__ESM_BUNDLER__ && !__TEST__) {
}

// register message compiler for jit compilation
if (__FEATURE_JIT_COMPILATION__) {
registerMessageCompiler(compile)
}
registerMessageCompiler(compile)

// register message resolver at @intlify/core
registerMessageResolver(resolveValue)
Expand Down
1 change: 0 additions & 1 deletion packages/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ declare let __FEATURE_PROD_VUE_DEVTOOLS__: boolean
declare let __FEATURE_PROD_INTLIFY_DEVTOOLS__: boolean
declare let __FEATURE_LEGACY_API__: boolean
declare let __FEATURE_FULL_INSTALL__: boolean
declare let __FEATURE_JIT_COMPILATION__: boolean
declare let __FEATURE_DROP_MESSAGE_COMPILER__: boolean
12 changes: 1 addition & 11 deletions packages/petite-vue-i18n/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { getGlobalThis } from '@intlify/shared'
import {
setDevToolsHook,
registerMessageCompiler,
compileToFunction,
compile
} from '@intlify/core-base'
import { initDev, initFeatureFlags } from '../../vue-i18n-core/src/misc'
Expand All @@ -12,16 +11,7 @@ if (__ESM_BUNDLER__ && !__TEST__) {
}

// register message compiler at petite-vue-i18n
if (
__ESM_BROWSER__ ||
__NODE_JS__ ||
__GLOBAL__ ||
__FEATURE_JIT_COMPILATION__
) {
registerMessageCompiler(compile)
} else {
registerMessageCompiler(compileToFunction)
}
registerMessageCompiler(compile)

export {
Path,
Expand Down
4 changes: 1 addition & 3 deletions packages/petite-vue-i18n/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ if (__ESM_BUNDLER__ && !__TEST__) {
}

// register message compiler for jit compilation
if (__FEATURE_JIT_COMPILATION__) {
registerMessageCompiler(compile)
}
registerMessageCompiler(compile)

export {
Path,
Expand Down

0 comments on commit 8b77ee3

Please sign in to comment.