Skip to content

Commit

Permalink
improvement: vue-i18n re-packaging (#222)
Browse files Browse the repository at this point in the history
* improvement: vue-i18n re-packaging

* fix: rollup config
  • Loading branch information
kazupon authored Dec 11, 2020
1 parent 0ad8896 commit 6b4a118
Show file tree
Hide file tree
Showing 81 changed files with 562 additions and 9,231 deletions.
16 changes: 6 additions & 10 deletions benchmark/complex.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
const {
baseCompile
} = require('../packages/message-compiler/dist/message-compiler.cjs.prod.js')
const {
clearCompileCache
} = require('../packages/runtime/dist/runtime.cjs.prod.js')
const { baseCompile } = require('../packages/message-compiler')
const {
translate,
createRuntimeContext
} = require('../packages/core/dist/core.cjs.prod.js')
const { createI18n } = require('../packages/vue-i18n/dist/vue-i18n.cjs.prod.js')
createCoreContext,
clearCompileCache
} = require('../packages/core')
const { createI18n } = require('../packages/vue-i18n')
const convertHrtime = require('convert-hrtime')

const data = require('./complex.json')
Expand All @@ -27,7 +23,7 @@ console.log(`ms: ${end.milliseconds - start.milliseconds}`)
console.log()

console.log(`resolve time with core: ${len} resources`)
const ctx = createRuntimeContext({
const ctx = createCoreContext({
locale: 'en',
modifiers: {
caml: val => val
Expand Down
17 changes: 7 additions & 10 deletions benchmark/simple.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
const {
baseCompile
} = require('../packages/message-compiler/dist/message-compiler.cjs.prod.js')
const {
clearCompileCache
} = require('../packages/runtime/dist/runtime.cjs.prod.js')
const { baseCompile } = require('../packages/message-compiler')
const {
translate,
createRuntimeContext
} = require('../packages/core/dist/core.cjs.prod.js')
const { createI18n } = require('../packages/vue-i18n/dist/vue-i18n.cjs.prod.js')
createCoreContext,
clearCompileCache
} = require('../packages/core')
const { createI18n } = require('../packages/vue-i18n')
const convertHrtime = require('convert-hrtime')

const simpleData = require('./simple.json')
Expand All @@ -28,7 +24,7 @@ console.log(`ms: ${end.milliseconds - start.milliseconds}`)
console.log()

console.log(`resolve time with core: ${len} resources`)
const ctx = createRuntimeContext({
const ctx = createCoreContext({
locale: 'en',
messages: {
en: simpleData
Expand All @@ -46,6 +42,7 @@ console.log()

console.log(`resolve time with composition: ${len} resources`)
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: simpleData
Expand Down
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ module.exports = {
__VERSION__: require('./package.json').version,
__BROWSER__: false,
__GLOBAL__: false,
__RUNTIME__: false,
__WARN_LABEL__: `'vue-i18n'`,
__BUNDLE_FILENAME__: 'test.bundle.js',
__ESM_BUNDLER__: true,
__ESM_BROWSER__: false,
__NODE_JS__: true,
Expand Down Expand Up @@ -93,6 +96,7 @@ module.exports = {

// A map from regular expressions to module names that allow to stub out resources with a single module
moduleNameMapper: {
'^@intlify/core/src/runtime$': '<rootDir>/packages/core/src/index.ts',
'^@intlify/(.*?)$': '<rootDir>/packages/$1/src',
'vue-i18n': '<rootDir>/packages/vue-i18n/src'
},
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"scripts": {
"benchmark": "node ./benchmark/index.js",
"build": "node scripts/build.js",
"build:size": "npm-run-all --parallel build:size-*",
"build:size-core": "rollup -c packages/size-check-core/rollup.config.js",
"build:size-vue-i18n": "rollup -c packages/size-check-vue-i18n/rollup.config.js",
"build:sourcemap": "yarn build --sourcemap",
"build:type": "yarn build --types && tail -n +19 ./packages/vue-i18n/src/vue.d.ts >> ./packages/vue-i18n/dist/vue-i18n.d.ts",
"clean": "npm-run-all --parallel clean:*",
Expand All @@ -19,7 +22,6 @@
"clean:coverage": "rm -rf ./coverage",
"clean:dist": "rm -rf ./dist/**",
"clean:docs": "rm -rf ./docs/api/vue-i18n**.md",
"clean:size": "rm -rf ./size-check/**/dist ./size-check/**/node_modules",
"clean:type": "rm -rf ./types/** ./temp ./dist/vue-i18n.d.ts",
"coverage": "opener coverage/lcov-report/index.html",
"dev": "node scripts/dev.js",
Expand Down
40 changes: 40 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,46 @@

The intlify core module for i18n

## Which dist file to use?

### From CDN or without a Bundler

- **`core(.runtime).global(.prod).js`**:
- For direct use via `<script src="...">` in the browser. Exposes the `IntlifyCore` global
- Note that global builds are not [UMD](https://github.com/umdjs/umd) builds. They are built as [IIFEs](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) and is only meant for direct use via `<script src="...">`
- In-browser locale messages compilation:
- **`core.global.js`** is the "full" build that includes both the compiler and the runtime so it supports compiling locale messages on the fly
- **`core.runtime.global.js`** contains only the runtime and requires locale messages to be pre-compiled during a build step
- Inlines internal the bellow packages - i.e. it’s a single file with no dependencies on other files. This means you **must** import everything from this file and this file only to ensure you are getting the same instance of code
- `@intlify/shared`
- `@intlify/message-resolver`
- `@intlify/message-compiler`
- Contains hard-coded prod/dev branches, and the prod build is pre-minified. Use the `*.prod.js` files for production

- **`core(.runtime).esm-browser(.prod).js`**:
- For usage via native ES modules imports (in browser via `<script type="module">`)
- Shares the same runtime compilation, dependency inlining and hard-coded prod/dev behavior with the global build

### With a Bundler

- **`core(.runtime).esm-bundler.js`**:
- For use with bundlers like `webpack`, `rollup` and `parcel`
- Leaves prod/dev branches with `process.env.NODE_ENV` guards (must be replaced by bundler)
- Does not ship minified builds (to be done together with the rest of the code after bundling)
- Imports dependencies (e.g. `@intlify/message-compiler`, `@intlify/message-resolver`)
- Imported dependencies are also `esm-bundler` builds and will in turn import their dependencies (e.g. `@intlify/message-compiler` imports `@intlify/shared`)
- This means you **can** install/import these deps individually without ending up with different instances of these dependencies, but you must make sure they all resolve to the same version
- In-browser locale messages compilation:
- **`core.runtime.esm-bundler.js` (default)** is runtime only, and requires all locale messages to be pre-compiled. This is the default entry for bundlers (via `module` field in `package.json`) because when using a bundler templates are typically pre-compiled (e.g. in `*.json` files)
- **`core.esm-bundler.js`**: includes the runtime compiler. Use this if you are using a bundler but still want locale messages compilation (e.g. templates via inline JavaScript strings)

### For Node.js (Server-Side)

- **`core.cjs(.prod).js`**:
- For use in Node.js via `require()`
- If you bundle your app with webpack with `target: 'node'` and properly externalize `@intlify/core`, this is the build that will be loaded
- The dev/prod files are pre-built, but the appropriate file is automatically required based on `process.env.NODE_ENV`

## :copyright: License

[MIT](http://opensource.org/licenses/MIT)
5 changes: 4 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@
"name": "IntlifyCore",
"formats": [
"esm-bundler",
"esm-bundler-runtime",
"esm-browser",
"esm-browser-runtime",
"cjs",
"global"
"global",
"global-runtime"
]
},
"sideEffects": false
Expand Down
69 changes: 69 additions & 0 deletions packages/core/src/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { warn, format, isBoolean } from '@intlify/shared'
import { baseCompile, defaultOnError } from '@intlify/message-compiler'

import type { CompileOptions, CompileError } from '@intlify/message-compiler'
import type { MessageFunction, MessageFunctions } from '@intlify/runtime'

const RE_HTML_TAG = /<\/?[\w\s="/.':;#-\/]+>/
const WARN_MESSAGE = `Detected HTML in '{source}' message. Recommend not using HTML messages to avoid XSS.`

function checkHtmlMessage(source: string, options: CompileOptions): void {
const warnHtmlMessage = isBoolean(options.warnHtmlMessage)
? options.warnHtmlMessage
: true
if (warnHtmlMessage && RE_HTML_TAG.test(source)) {
warn(format(WARN_MESSAGE, { source }))
}
}

const defaultOnCacheKey = (source: string): string => source
let compileCache: unknown = Object.create(null)

/** @internal */
export function clearCompileCache(): void {
compileCache = Object.create(null)
}

/** @internal */
export function compileToFunction<T = string>(
source: string,
options: CompileOptions = {}
): MessageFunction<T> {
if (__RUNTIME__) {
__DEV__ &&
warn(
`Runtime compilation is not supported in ${
__BUNDLE_FILENAME__ || 'N/A'
}.`
)
return (() => source) as MessageFunction<T>
} else {
// check HTML message
__DEV__ && checkHtmlMessage(source, options)

// check caches
const onCacheKey = options.onCacheKey || defaultOnCacheKey
const key = onCacheKey(source)
const cached = (compileCache as MessageFunctions<T>)[key]
if (cached) {
return cached
}

// compile error detecting
let occured = false
const onError = options.onError || defaultOnError
options.onError = (err: CompileError): void => {
occured = true
onError(err)
}

// compile
const { code } = baseCompile(source, options)

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

// if occured compile error, don't cache
return !occured ? ((compileCache as MessageFunctions<T>)[key] = msg) : msg
}
}
14 changes: 11 additions & 3 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
isPlainObject,
isObject
} from '@intlify/shared'
import { compile } from '@intlify/runtime'
import { DevToolsTimelineEvents } from './debugger/constants'
import { CoreWarnCodes, getWarnMessage } from './warnings'

Expand Down Expand Up @@ -133,7 +132,7 @@ export interface CoreTranslationContext<Messages = {}, Message = string>
processor: MessageProcessor<Message> | null
warnHtmlMessage: boolean
escapeParameter: boolean
messageCompiler: MessageCompiler<Message>
messageCompiler: MessageCompiler<Message> | null
}

/** @internal */
Expand Down Expand Up @@ -188,6 +187,15 @@ function getDefaultLinkedModifiers<
}
}

let _compiler: unknown | null

/** @internal */
export function registerMessageCompiler<Message>(
compiler: MessageCompiler<Message>
): void {
_compiler = compiler
}

/** @internal */
export function createCoreContext<
Message = string,
Expand Down Expand Up @@ -257,7 +265,7 @@ export function createCoreContext<
const escapeParameter = !!options.escapeParameter
const messageCompiler = isFunction(options.messageCompiler)
? options.messageCompiler
: compile
: _compiler
const onWarn = isFunction(options.onWarn) ? options.onWarn : warn

// setup internal options
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { registerMessageCompiler } from './context'
import { compileToFunction } from './compile'

// register message compiler at @intlify/core
registerMessageCompiler(compileToFunction)

export * from './context'
export * from './compile'
export * from './translate'
export * from './datetime'
export * from './number'
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// NOTE: for runtime only buidling & vue-i18n direct inmporting
export * from './context'
export * from './compile'
export * from './translate'
export * from './datetime'
export * from './number'
export * from './debugger'
export * from './warnings'
export * from './errors'
export * from './types'
10 changes: 9 additions & 1 deletion packages/core/src/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
generateCodeFrame,
escapeHtml,
inBrowser,
warn,
mark,
measure,
isObject
Expand Down Expand Up @@ -305,6 +306,13 @@ export function translate<Messages, Message = string>(
return unresolving ? NOT_REOSLVED : (key as MessageType<Message>)
}

if (__RUNTIME__ && isString(format) && context.messageCompiler == null) {
warn(
`Message format compilation is not supported in this build, because message compiler isn't included, you need to pre-compilation all message format.`
)
return key as MessageType<Message>
}

// setup compile error detecting
let occured = false
const errorDetector = () => {
Expand Down Expand Up @@ -487,7 +495,7 @@ function compileMessasgeFormat<Messages, Message>(
mark && mark(startTag)
}

const msg = messageCompiler(
const msg = messageCompiler!(
format as string,
getCompileOptions(
context,
Expand Down
6 changes: 6 additions & 0 deletions packages/core/test/datetime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { Availabilities } from '../src/intl'
import { createCoreContext as context, NOT_REOSLVED } from '../src/context'
import { datetime } from '../src/datetime'
import { CoreErrorCodes, errorMessages } from '../src/errors'
import { registerMessageCompiler } from '../src/context'
import { compileToFunction } from '../src/compile'

const datetimeFormats = {
'en-US': {
Expand Down Expand Up @@ -55,6 +57,10 @@ const datetimeFormats = {

const dt = new Date(Date.UTC(2012, 11, 20, 3, 0, 0))

beforeEach(() => {
registerMessageCompiler(compileToFunction)
})

test('datetime value', () => {
const mockAvailabilities = Availabilities as jest.Mocked<
typeof Availabilities
Expand Down
6 changes: 6 additions & 0 deletions packages/core/test/number.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { Availabilities } from '../src/intl'
import { createCoreContext as context, NOT_REOSLVED } from '../src/context'
import { number } from '../src/number'
import { CoreErrorCodes, errorMessages } from '../src/errors'
import { registerMessageCompiler } from '../src/context'
import { compileToFunction } from '../src/compile'

const numberFormats = {
'en-US': {
Expand Down Expand Up @@ -46,6 +48,10 @@ const numberFormats = {
}
}

beforeEach(() => {
registerMessageCompiler(compileToFunction)
})

test('value argument only', () => {
const mockAvailabilities = Availabilities as jest.Mocked<
typeof Availabilities
Expand Down
6 changes: 6 additions & 0 deletions packages/core/test/translate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import { warn } from '@intlify/shared'
import { createCoreContext as context, NOT_REOSLVED } from '../src/context'
import { translate } from '../src/translate'
import { CoreErrorCodes, errorMessages } from '../src/errors'
import { registerMessageCompiler } from '../src/context'
import { compileToFunction } from '../src/compile'

beforeEach(() => {
registerMessageCompiler(compileToFunction)
})

describe('features', () => {
test('simple text', () => {
Expand Down
Loading

0 comments on commit 6b4a118

Please sign in to comment.