diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..1ed2308e Binary files /dev/null and b/.DS_Store differ diff --git a/package.json b/package.json index d5f9497e..7b702acc 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "prettier": "prettier .prettierrc.js -w packages/**/*.{ts,tsx,js,jsx}", "build:inula": "pnpm -F openinula build", "test:inula": "pnpm -F openinula test", + "test:inula-intl": "pnpm -F inula-intl test", + "test:inula-request": "pnpm -F inula-request test", + "test:inula-router": "pnpm -F inula-router test", "build:inula-cli": "pnpm -F inula-cli build", "build:inula-intl": "pnpm -F inula-intl build", "build:inula-request": "pnpm -F inula-request build", @@ -22,46 +25,48 @@ ] }, "devDependencies": { - "@babel/core": "7.16.7", - "@babel/plugin-proposal-class-properties": "7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7", - "@babel/plugin-proposal-object-rest-spread": "7.16.7", - "@babel/plugin-proposal-optional-chaining": "7.16.7", - "@babel/plugin-proposal-private-methods": "7.16.7", - "@babel/plugin-proposal-private-property-in-object": "7.16.7", - "@babel/plugin-syntax-jsx": "7.16.7", - "@babel/plugin-transform-arrow-functions": "7.16.7", - "@babel/plugin-transform-block-scoped-functions": "7.16.7", - "@babel/plugin-transform-block-scoping": "7.16.7", - "@babel/plugin-transform-classes": "7.16.7", - "@babel/plugin-transform-computed-properties": "7.16.7", - "@babel/plugin-transform-destructuring": "7.16.7", - "@babel/plugin-transform-for-of": "7.16.7", - "@babel/plugin-transform-literals": "7.16.7", - "@babel/plugin-transform-object-assign": "7.16.7", - "@babel/plugin-transform-object-super": "7.16.7", - "@babel/plugin-transform-parameters": "7.16.7", - "@babel/plugin-transform-react-jsx": "7.16.7", - "@babel/plugin-transform-react-jsx-source": "^7.16.7", - "@babel/plugin-transform-runtime": "7.16.7", - "@babel/plugin-transform-shorthand-properties": "7.16.7", - "@babel/plugin-transform-spread": "7.16.7", - "@babel/plugin-transform-template-literals": "7.16.7", - "@babel/preset-env": "7.16.7", - "@babel/preset-typescript": "7.16.7", - "@babel/runtime": "7.16.7", - "@commitlint/cli": "^18.4.4", - "@commitlint/config-conventional": "^18.4.4", - "@rollup/plugin-babel": "^5.3.1", - "@rollup/plugin-node-resolve": "^13.3.0", + "@babel/core": "7.23.7", + "@babel/plugin-proposal-class-properties": "7.18.6", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6", + "@babel/plugin-proposal-object-rest-spread": "7.20.7", + "@babel/plugin-proposal-optional-chaining": "7.21.0", + "@babel/plugin-proposal-private-methods": "7.18.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.11", + "@babel/plugin-syntax-jsx": "7.23.3", + "@babel/plugin-transform-arrow-functions": "7.23.3", + "@babel/plugin-transform-block-scoped-functions": "7.23.3", + "@babel/plugin-transform-block-scoping": "7.23.4", + "@babel/plugin-transform-classes": "7.23.8", + "@babel/plugin-transform-computed-properties": "7.23.3", + "@babel/plugin-transform-destructuring": "7.23.3", + "@babel/plugin-transform-for-of": "7.23.6", + "@babel/plugin-transform-literals": "7.23.3", + "@babel/plugin-transform-object-assign": "7.23.3", + "@babel/plugin-transform-object-super": "7.23.3", + "@babel/plugin-transform-parameters": "7.23.3", + "@babel/plugin-transform-react-jsx": "7.23.4", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@babel/plugin-transform-runtime": "7.23.7", + "@babel/plugin-transform-shorthand-properties": "7.23.3", + "@babel/plugin-transform-spread": "7.23.3", + "@babel/plugin-transform-template-literals": "7.23.3", + "@babel/preset-env": "7.23.8", + "@babel/preset-typescript": "7.23.3", + "@babel/runtime": "7.23.8", + "@commitlint/cli": "^17.8.1", + "@commitlint/config-conventional": "^17.8.1", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^4.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^29.5.11", "@types/node": "^17.0.18", - "@typescript-eslint/eslint-plugin": "4.8.0", - "@typescript-eslint/parser": "4.8.0", - "babel-jest": "^27.5.1", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "6.18.1", + "@babel/parser": "^7.24.7", + "magic-string": "^0.30.10", + "babel-jest": "^29.7.0", "ejs": "^3.1.8", - "eslint": "7.13.0", + "eslint": "^8.56.0", "eslint-config-prettier": "^6.9.0", "eslint-plugin-jest": "^22.15.0", "eslint-plugin-no-function-declare-after-return": "^1.0.0", @@ -72,9 +77,13 @@ "lint-staged": "^15.2.0", "openinula": "workspace:*", "prettier": "^3.1.1", - "rollup": "^2.75.5", + "rollup": "^2.79.1", + "rollup-plugin-dts": "^6.1.0", "rollup-plugin-execute": "^1.1.1", "rollup-plugin-terser": "^7.0.2", + "rollup-plugin-esbuild": "^6.1.1", + "rollup-plugin-polyfill-node": "^0.13.0", + "ts-jest": "^29.1.1", "typescript": "^4.9.5" }, "engines": { diff --git a/packages/create-inula/lib/generators/Simple-app/templates/vite/README.md b/packages/create-inula/lib/generators/Simple-app/templates/vite/README.md deleted file mode 100644 index 542c363a..00000000 --- a/packages/create-inula/lib/generators/Simple-app/templates/vite/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# openinula + vite - -该模板提供了 `openinula` 工作在 `vite`的基础配置。 -> 请注意由于Vite插件有node版本限制,请使用`node -v`命令确认node版本大于等于node v18。 \ No newline at end of file diff --git a/packages/create-inula/lib/generators/Simple-app/templates/webpack/src/App.jsx b/packages/create-inula/lib/generators/Simple-app/templates/webpack/src/App.jsx deleted file mode 100644 index 75e326c4..00000000 --- a/packages/create-inula/lib/generators/Simple-app/templates/webpack/src/App.jsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import Inula from 'openinula'; -import './styles.css'; - -class App extends Inula.Component { - render() { - return ( -
-
-

欢迎来到 Inula 项目!

-

你已成功创建你的第一个 Inula 项目

-
-
-
-

开始吧

-

- 编辑 src/App.js 并保存以重新加载。 -

-
-
-

了解更多

-

- 要了解 Inula,查看{' '} - - Inula 官网 - -

-
-
-
- ); - } -} - -export default App; diff --git a/packages/create-inula/lib/generators/Simple-app/templates/webpack/src/index.jsx b/packages/create-inula/lib/generators/Simple-app/templates/webpack/src/index.jsx deleted file mode 100644 index c384f05a..00000000 --- a/packages/create-inula/lib/generators/Simple-app/templates/webpack/src/index.jsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import Inula from 'openinula'; -import App from './App'; - -Inula.render(, document.getElementById('root')); diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/index.js b/packages/create-inula/lib/generators/Simple-reactive-app/index.js deleted file mode 100644 index 2d52bc4b..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/index.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -const BasicGenerator = require('../../BasicGenerator'); - -class Generator extends BasicGenerator { - prompting() { - return this.prompt([ - { - type: 'list', - name: 'bundlerType', - message: 'Please select the build type', - choices: ['webpack', 'vite'], - }, - ]).then(props => { - this.prompts = props; - }); - } - - writing() { - const src = this.templatePath(this.prompts.bundlerType); - const dest = this.destinationPath(); - this.writeFiles(src, dest, { - context: { - ...this.prompts, - }, - }); - } -} - -module.exports = Generator; diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/meta.json b/packages/create-inula/lib/generators/Simple-reactive-app/meta.json deleted file mode 100644 index 8f8f9e66..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "description": "simple reactive app template." -} diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/README.md b/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/README.md deleted file mode 100644 index 542c363a..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# openinula + vite - -该模板提供了 `openinula` 工作在 `vite`的基础配置。 -> 请注意由于Vite插件有node版本限制,请使用`node -v`命令确认node版本大于等于node v18。 \ No newline at end of file diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/index.html b/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/index.html deleted file mode 100644 index 9f37946a..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - My Inula App - - -
- - - diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/package.json b/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/package.json deleted file mode 100644 index f74bdda6..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "inula-vite-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "start": "vite", - "build": "vite build" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "openinula": "0.0.0-experimental-20231201" - }, - "devDependencies": { - "@babel/core": "^7.21.4", - "@babel/preset-env": "^7.21.4", - "@babel/preset-react": "^7.18.6", - "@vitejs/plugin-react": "^3.1.0", - "@vitejs/plugin-react-refresh": "^1.3.6", - "babel-plugin-import": "^1.13.6", - "vite": "^4.2.1" - } -} diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/src/ReactiveComponent.jsx b/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/src/ReactiveComponent.jsx deleted file mode 100644 index cca83f05..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/src/ReactiveComponent.jsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import Inula, { useComputed, useReactive, useRef } from 'openinula'; - -function ReactiveComponent() { - const renderCount = ++useRef(0).current; - - const data = useReactive({ count: 0 }); - const countText = useComputed(() => { - return `计时: ${data.count.get()}`; - }); - - setInterval(() => { - data.count.set(c => c + 1); - }, 1000); - - return ( -
-
{countText}
-
组件渲染次数:{renderCount}
-
- ); -} - -export default ReactiveComponent; diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/src/index.css b/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/src/index.css deleted file mode 100644 index f08fdb8a..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/src/index.css +++ /dev/null @@ -1,57 +0,0 @@ -* { - box-sizing: border-box; -} - -body, -html { - margin: 0; - padding: 0; - font-family: 'Montserrat', sans-serif; - line-height: 1.6; - color: #fff; - background: linear-gradient(120deg, #6a11cb 0%, #2575fc 100%); - height: 100vh; - display: flex; - align-items: center; - justify-content: center; -} - -.container { - text-align: center; -} - -.hero-title { - font-size: 3em; - margin-bottom: 20px; -} - -.hero-subtitle { - font-size: 1.5em; - margin-bottom: 50px; -} - -.content { - display: flex; - justify-content: space-between; - align-items: center; - margin: 1vh; -} - -.card { - background: rgba(255, 255, 255, 0.1); - border-radius: 5px; - padding: 20px; - width: 100%; - box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(4px); -} - -.card h2, -.card p { - color: #fff; -} - -.card a { - color: #fff; - text-decoration: underline; -} diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/src/index.jsx b/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/src/index.jsx deleted file mode 100644 index bab57690..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/src/index.jsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import Inula from 'openinula'; -import ReactiveComponent from './ReactiveComponent'; -import './index.css'; - -function App() { - return ( -
-
-

欢迎来到 Inula 项目!

-

你已成功创建你的第一个响应式 Inula 项目

-
-
-
- -
-
-
-
-

了解更多

-

- 要了解 Inula,查看{' '} - - Inula 官网 - -

-
-
-
- ); -} - -Inula.render(, document.getElementById('root')); diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/vite.config.js b/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/vite.config.js deleted file mode 100644 index 43065ece..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/vite/vite.config.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import react from '@vitejs/plugin-react'; - -let alias = { - react: 'openinula', // 新增 - 'react-dom': 'openinula', // 新增 - 'react/jsx-dev-runtime': 'openinula/jsx-dev-runtime', -}; - -export default { - plugins: [react()], - resolve: { - alias, - }, -}; diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/package.json b/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/package.json deleted file mode 100644 index 783f626a..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "inula-webpack-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "start": "webpack serve --mode development", - "build": "webpack --mode production" - }, - "author": "", - "license": "ISC", - "dependencies": { - "openinula": "0.0.0-experimental-20231201" - }, - "devDependencies": { - "@babel/core": "^7.21.4", - "@babel/preset-env": "^7.21.4", - "@babel/preset-react": "^7.18.6", - "babel-loader": "^9.1.2", - "css-loader": "^6.7.3", - "file-loader": "^6.2.0", - "html-webpack-plugin": "^5.5.0", - "style-loader": "^3.3.2", - "url-loader": "^4.1.1", - "webpack": "^5.77.0", - "webpack-cli": "^5.0.1", - "webpack-dev-server": "^4.13.2" - } -} diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/App.jsx b/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/App.jsx deleted file mode 100644 index a8b52c96..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/App.jsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import Inula from 'openinula'; -import ReactiveComponent from './ReactiveComponent'; -import './styles.css'; - -class App extends Inula.Component { - render() { - return ( -
-
-

欢迎来到 Inula 项目!

-

你已成功创建你的第一个响应式 Inula 项目

-
-
-
- -
-
-
-
-

了解更多

-

- 要了解 Inula,查看{' '} - - Inula 官网 - -

-
-
-
- ); - } -} - -export default App; diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/ReactiveComponent.jsx b/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/ReactiveComponent.jsx deleted file mode 100644 index cca83f05..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/ReactiveComponent.jsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import Inula, { useComputed, useReactive, useRef } from 'openinula'; - -function ReactiveComponent() { - const renderCount = ++useRef(0).current; - - const data = useReactive({ count: 0 }); - const countText = useComputed(() => { - return `计时: ${data.count.get()}`; - }); - - setInterval(() => { - data.count.set(c => c + 1); - }, 1000); - - return ( -
-
{countText}
-
组件渲染次数:{renderCount}
-
- ); -} - -export default ReactiveComponent; diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/index.html b/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/index.html deleted file mode 100644 index c966e725..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Inula App - - -
- - - diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/index.jsx b/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/index.jsx deleted file mode 100644 index c384f05a..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/index.jsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import Inula from 'openinula'; -import App from './App'; - -Inula.render(, document.getElementById('root')); diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/styles.css b/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/styles.css deleted file mode 100644 index f08fdb8a..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/src/styles.css +++ /dev/null @@ -1,57 +0,0 @@ -* { - box-sizing: border-box; -} - -body, -html { - margin: 0; - padding: 0; - font-family: 'Montserrat', sans-serif; - line-height: 1.6; - color: #fff; - background: linear-gradient(120deg, #6a11cb 0%, #2575fc 100%); - height: 100vh; - display: flex; - align-items: center; - justify-content: center; -} - -.container { - text-align: center; -} - -.hero-title { - font-size: 3em; - margin-bottom: 20px; -} - -.hero-subtitle { - font-size: 1.5em; - margin-bottom: 50px; -} - -.content { - display: flex; - justify-content: space-between; - align-items: center; - margin: 1vh; -} - -.card { - background: rgba(255, 255, 255, 0.1); - border-radius: 5px; - padding: 20px; - width: 100%; - box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(4px); -} - -.card h2, -.card p { - color: #fff; -} - -.card a { - color: #fff; - text-decoration: underline; -} diff --git a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/webpack.config.js b/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/webpack.config.js deleted file mode 100644 index 8b46f7a5..00000000 --- a/packages/create-inula/lib/generators/Simple-reactive-app/templates/webpack/webpack.config.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -const path = require('path'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); - -module.exports = { - entry: './src/index.jsx', - output: { - path: path.resolve(__dirname, 'dist'), - filename: 'bundle.js', - }, - module: { - rules: [ - { - test: /\.(js|jsx)$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader', - options: { - presets: [ - '@babel/preset-env', - [ - '@babel/preset-react', - { - runtime: 'automatic', // 新增 - importSource: 'openinula', // 新增 - }, - ], - ], - }, - }, - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'], - }, - { - test: /\.(png|jpe?g|gif)$/i, - use: [ - { - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'images/', - publicPath: 'images/', - }, - }, - ], - }, - { - test: /\.(woff|woff2|eot|ttf|otf)$/, - use: ['file-loader'], - }, - ], - }, - plugins: [ - new HtmlWebpackPlugin({ - template: path.resolve(__dirname, 'src/index.html'), - filename: 'index.html', - }), - ], - devServer: { - static: path.join(__dirname, 'dist'), - compress: true, - port: 9000, - open: true, - }, - resolve: { - extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'], - }, -}; diff --git a/packages/create-inula/lib/run.js b/packages/create-inula/lib/run.js index d0ad06cd..a48cdacd 100644 --- a/packages/create-inula/lib/run.js +++ b/packages/create-inula/lib/run.js @@ -32,8 +32,8 @@ const generatorType = fs }); const runGenerator = async (templatePath, { name = '', cwd = process.cwd(), args = {} }) => { + let currentPath; return new Promise(resolve => { - let currentPath; if (name) { mkdirp.sync(name); currentPath = path.join(cwd, name); diff --git a/packages/inula-cli/externals.d.ts b/packages/inula-cli/externals.d.ts index 2fe4981b..ab03cf87 100644 --- a/packages/inula-cli/externals.d.ts +++ b/packages/inula-cli/externals.d.ts @@ -14,4 +14,3 @@ */ declare module 'crequire'; - diff --git a/packages/inula-cli/src/builtInPlugins/command/build/build.ts b/packages/inula-cli/src/builtInPlugins/command/build/build.ts deleted file mode 100644 index ff9a89f8..00000000 --- a/packages/inula-cli/src/builtInPlugins/command/build/build.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import webpack from 'webpack'; -import { build } from 'vite'; - -export default (api: any) => { - api.registerCommand({ - name: 'build', - description: 'build application for production', - initialState: api.buildConfig, - fn: async function (args: any, state: any) { - switch (api.compileMode) { - case 'webpack': - if (state) { - api.applyHook({ name: 'beforeCompile', args: state }); - state.forEach((s: any) => { - webpack(s.config, (err: any, stats: any) => { - if (err || stats.hasErrors()) { - api.logger.error(`Build failed.err: ${err}, stats:${stats}`); - } - }); - }); - } else { - api.logger.error(`Build failed. Can't find build config.`); - } - break; - case 'vite': - if (state) { - api.applyHook({ name: 'beforeCompile' }); - build(state); - } else { - api.logger.error(`Build failed. Can't find build config.`); - } - break; - } - }, - }); -}; diff --git a/packages/inula-cli/src/builtInPlugins/command/dev/buildDev.ts b/packages/inula-cli/src/builtInPlugins/command/dev/buildDev.ts index 400fbe53..9ffccf76 100644 --- a/packages/inula-cli/src/builtInPlugins/command/dev/buildDev.ts +++ b/packages/inula-cli/src/builtInPlugins/command/dev/buildDev.ts @@ -57,7 +57,7 @@ export default (api: API) => { api.applyHook({ name: 'afterStartDevServer' }); }); } else { - api.logger.error('Can\'t find config'); + api.logger.error("Can't find config"); } break; case 'vite': @@ -70,7 +70,7 @@ export default (api: API) => { server.printUrls(); }); } else { - api.logger.error('Can\'t find config'); + api.logger.error("Can't find config"); } break; default: diff --git a/packages/inula-cli/src/builtInPlugins/command/generate/generate.ts b/packages/inula-cli/src/builtInPlugins/command/generate/generate.ts index 777aefe8..e71f4f96 100644 --- a/packages/inula-cli/src/builtInPlugins/command/generate/generate.ts +++ b/packages/inula-cli/src/builtInPlugins/command/generate/generate.ts @@ -33,7 +33,7 @@ export default (api: API) => { args._.shift(); } if (args._.length === 0) { - api.logger.warn('Can\'t find any generate options.'); + api.logger.warn("Can't find any generate options."); return; } diff --git a/packages/inula-intl/.babelrc b/packages/inula-intl/.babelrc new file mode 100644 index 00000000..3044a5a8 --- /dev/null +++ b/packages/inula-intl/.babelrc @@ -0,0 +1,42 @@ +{ + "presets": [ + ["@babel/preset-env", { + "targets": { + "node": "current" + } + }], + "@babel/preset-typescript", + "@babel/preset-react" + ], + "plugins": [ + "@babel/plugin-syntax-jsx", + [ + "@babel/plugin-transform-react-jsx", + { + "runtime": "automatic", + "importSource": "openinula" + } + ], + ["@babel/plugin-proposal-class-properties", { "loose": true }], + ["@babel/plugin-proposal-private-methods", { "loose": true }], + ["@babel/plugin-proposal-private-property-in-object", { "loose": true }], + "@babel/plugin-transform-object-assign", + "@babel/plugin-transform-object-super", + ["@babel/plugin-proposal-object-rest-spread", { "loose": true, "useBuiltIns": true }], + ["@babel/plugin-transform-template-literals", { "loose": true }], + "@babel/plugin-transform-arrow-functions", + "@babel/plugin-transform-literals", + "@babel/plugin-transform-for-of", + "@babel/plugin-transform-block-scoped-functions", + "@babel/plugin-transform-classes", + "@babel/plugin-transform-shorthand-properties", + "@babel/plugin-transform-computed-properties", + "@babel/plugin-transform-parameters", + ["@babel/plugin-transform-spread", { "loose": true, "useBuiltIns": true }], + ["@babel/plugin-transform-block-scoping", { "throwIfClosureRequired": false }], + ["@babel/plugin-transform-destructuring", { "loose": true, "useBuiltIns": true }], + "@babel/plugin-transform-runtime", + "@babel/plugin-proposal-nullish-coalescing-operator", + "@babel/plugin-proposal-optional-chaining" + ] +} diff --git a/packages/inula-intl/babel.config.js b/packages/inula-intl/babel.config.js deleted file mode 100644 index 7f94d715..00000000 --- a/packages/inula-intl/babel.config.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -const { preset } = require('./jest.config'); -module.exports = { - presets: [ - [ - '@babel/preset-env', - { targets: { node: 'current' } }, - ], - ['@babel/preset-typescript'], - [ - '@babel/preset-react', - { - runtime: 'automatic', - importSource: 'openinula', - }, - ], - ], -}; diff --git a/packages/inula-intl/build-type.js b/packages/inula-intl/build-type.js index 0be14358..8e7f5dbd 100644 --- a/packages/inula-intl/build-type.js +++ b/packages/inula-intl/build-type.js @@ -7,12 +7,12 @@ function deleteFolder(filePath) { if (fs.lstatSync(filePath).isDirectory()) { const files = fs.readdirSync(filePath); files.forEach(file => { - const nectFilePath = path.join(filePath, file); - const states = fs.lstatSync(nectFilePath); + const nextFilePath = path.join(filePath, file); + const states = fs.lstatSync(nextFilePath); if (states.isDirectory()) { - deleteFolder(nectFilePath); + deleteFolder(nextFilePath); } else { - fs.unlinkSync(nectFilePath); + fs.unlinkSync(nextFilePath); } }); fs.rmdirSync(filePath); @@ -31,12 +31,12 @@ export function cleanUp(folders) { return { name: 'clean-up', buildEnd() { - folders.forEach(folder => deleteFolder(folder)); + folders.forEach(f => deleteFolder(f)); }, }; } -function builderTypeConfig() { +function buildTypeConfig() { return { input: './build/@types/index.d.ts', output: { @@ -47,4 +47,4 @@ function builderTypeConfig() { }; } -export default [builderTypeConfig()]; +export default [buildTypeConfig()]; diff --git a/packages/inula-intl/example/App.tsx b/packages/inula-intl/example/App.tsx index 8c460c57..4dfc8c3e 100644 --- a/packages/inula-intl/example/App.tsx +++ b/packages/inula-intl/example/App.tsx @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -import Inula, { useState } from 'openinula'; +import { useState } from 'openinula'; import { IntlProvider } from '../index'; import zh from './locale/zh'; import en from './locale/en'; @@ -32,23 +32,29 @@ const App = () => { const message = locale === 'zh' ? zh : en; return ( - -
Inula-Intl API Test Demo
- -
- - - -
+ <> + +
Inula-Intl API Test Demo
+
+ + + +
+
+ {/**/} + +
+
+ +
+
- +
+
-
- -
-
+ ); }; diff --git a/packages/inula-intl/example/components/Example1.tsx b/packages/inula-intl/example/components/Example1.tsx index 0befb70e..9562fc10 100644 --- a/packages/inula-intl/example/components/Example1.tsx +++ b/packages/inula-intl/example/components/Example1.tsx @@ -13,16 +13,16 @@ * See the Mulan PSL v2 for more details. */ -import Inula from 'openinula'; import { useIntl } from '../../index'; const Example1 = () => { - const { i18n } = useIntl(); + const i18n = useIntl(); return (

useIntl方式测试Demo

{i18n.formatMessage({ id: 'text1' })}
+
{i18n.$t({ id: 'text1' })}
); }; diff --git a/packages/inula-intl/example/components/Example2.tsx b/packages/inula-intl/example/components/Example2.tsx index 74f73423..3aa28a6f 100644 --- a/packages/inula-intl/example/components/Example2.tsx +++ b/packages/inula-intl/example/components/Example2.tsx @@ -12,7 +12,6 @@ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. */ -import Inula from 'openinula'; import { FormattedMessage } from '../../index'; const Example2 = () => { @@ -22,6 +21,9 @@ const Example2 = () => {
         
       
+
+        123, testComponent2: 456 }} />
+      
); }; diff --git a/packages/inula-intl/example/components/Example3.tsx b/packages/inula-intl/example/components/Example3.tsx index 27fdd904..70d58366 100644 --- a/packages/inula-intl/example/components/Example3.tsx +++ b/packages/inula-intl/example/components/Example3.tsx @@ -13,7 +13,6 @@ * See the Mulan PSL v2 for more details. */ -import Inula from 'openinula'; import { FormattedMessage } from '../../index'; const Example3 = props => { diff --git a/packages/inula-intl/example/components/Example4.tsx b/packages/inula-intl/example/components/Example4.tsx index ec717934..f56cc4bf 100644 --- a/packages/inula-intl/example/components/Example4.tsx +++ b/packages/inula-intl/example/components/Example4.tsx @@ -13,7 +13,6 @@ * See the Mulan PSL v2 for more details. */ -import Inula from 'openinula'; import { createIntl } from '../../index'; const Example4 = props => { diff --git a/packages/inula-intl/example/components/Example5.tsx b/packages/inula-intl/example/components/Example5.tsx index 6b343e81..897f1a17 100644 --- a/packages/inula-intl/example/components/Example5.tsx +++ b/packages/inula-intl/example/components/Example5.tsx @@ -13,23 +13,16 @@ * See the Mulan PSL v2 for more details. */ -import Inula, { Component } from 'openinula'; import { injectIntl } from '../../index'; -class Example5 extends Component { - public constructor(props: any, context) { - super(props, context); - } - - render() { - const { intl } = this.props as any; - return ( -
-

injectIntl方式测试Demo

-
{intl.formatMessage({ id: 'text4' })}
-
- ); - } -} - +const Example5 = ({ intl }) => { + // 使用intl.formatMessage来获取国际化消息 + console.log(intl + '------------intl-------------'); + return ( +
+

injectIntl方式测试Demo

+
{intl.formatMessage({ id: 'text4' })}
+
+ ); +}; export default injectIntl(Example5); diff --git a/packages/inula-intl/example/components/Example6.tsx b/packages/inula-intl/example/components/Example6.tsx index 98a86729..ff220b4c 100644 --- a/packages/inula-intl/example/components/Example6.tsx +++ b/packages/inula-intl/example/components/Example6.tsx @@ -13,7 +13,6 @@ * See the Mulan PSL v2 for more details. */ -import Inula from 'openinula'; import { createIntl, createIntlCache, RawIntlProvider } from '../../index'; import Example6Child from './Example6Child'; @@ -21,7 +20,7 @@ const Example6 = (props: any) => { const { locale, messages } = props; const cache = createIntlCache(); - let i18n = createIntl({ locale: locale, messages: messages }, cache); + const i18n = createIntl({ locale: locale, messages: messages }, cache); return ( diff --git a/packages/inula-intl/example/components/Example6Child.tsx b/packages/inula-intl/example/components/Example6Child.tsx index 94e78714..6c19c105 100644 --- a/packages/inula-intl/example/components/Example6Child.tsx +++ b/packages/inula-intl/example/components/Example6Child.tsx @@ -15,7 +15,7 @@ import { useIntl } from '../../index'; -const Example6Child = (props: any) => { +const Example6Child = () => { const { formatMessage } = useIntl(); return ( diff --git a/packages/inula-intl/example/index.tsx b/packages/inula-intl/example/index.tsx index f0ab1550..97910e39 100644 --- a/packages/inula-intl/example/index.tsx +++ b/packages/inula-intl/example/index.tsx @@ -12,7 +12,7 @@ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. */ -import * as Inula from 'openinula'; +import Inula from 'openinula'; import App from './App'; function render() { diff --git a/packages/inula-intl/example/locale/en.ts b/packages/inula-intl/example/locale/en.ts index 21462b48..3cde2724 100644 --- a/packages/inula-intl/example/locale/en.ts +++ b/packages/inula-intl/example/locale/en.ts @@ -19,4 +19,5 @@ export default { text2: 'Welcome to the Inula-Intl component!', text3: 'Welcome to the Inula-Intl component!', text4: 'Welcome to the Inula-Intl component!', + text5: 'Render a component {testComponent1} {testComponent2}!', }; diff --git a/packages/inula-intl/example/locale/zh.ts b/packages/inula-intl/example/locale/zh.ts index 4051f195..d392b30c 100644 --- a/packages/inula-intl/example/locale/zh.ts +++ b/packages/inula-intl/example/locale/zh.ts @@ -18,4 +18,5 @@ export default { text2: '欢迎使用国际化组件!', text3: '欢迎使用国际化组件!', text4: '欢迎使用国际化组件!', + text5: '渲染一个组件 {testComponent1} {testComponent2}!', }; diff --git a/packages/inula-intl/index.ts b/packages/inula-intl/index.ts index 776255b8..401e582e 100644 --- a/packages/inula-intl/index.ts +++ b/packages/inula-intl/index.ts @@ -22,7 +22,7 @@ import I18nProvider from './src/core/components/I18nProvider'; import injectIntl, { I18nContext, InjectProvider } from './src/core/components/InjectI18n'; import useI18n from './src/core/hook/useI18n'; import createI18n from './src/core/createI18n'; -import { InjectedIntl, MessageDescriptor } from './src/types/interfaces'; +import { MessageDescriptor } from './src/types/interfaces'; // 函数API export { I18n, @@ -36,7 +36,7 @@ export { // 组件 export { FormattedMessage, - I18nContext, + I18nContext as IntlContext, I18nProvider as IntlProvider, injectIntl as injectIntl, InjectProvider as RawIntlProvider, @@ -64,7 +64,3 @@ export function defineMessages(msg: T): T { return msg; } - -export interface InjectedIntlProps { - intl: InjectedIntl; -} diff --git a/packages/inula-intl/jest.config.js b/packages/inula-intl/jest.config.js index 84592edf..6c82cf3c 100644 --- a/packages/inula-intl/jest.config.js +++ b/packages/inula-intl/jest.config.js @@ -13,7 +13,7 @@ * See the Mulan PSL v2 for more details. */ -module.exports = { +export default { coverageDirectory: 'coverage', resetModules: true, preset: 'ts-jest/presets/js-with-ts', @@ -30,8 +30,10 @@ module.exports = { globals: { 'ts-jest': { tsconfig: 'tsconfig.json', + diagnostics: false, }, }, + testPathIgnorePatterns: ['\\\\node_modules\\\\'], testEnvironment: 'jsdom', }; diff --git a/packages/inula-intl/package.json b/packages/inula-intl/package.json index 71793344..6474226c 100644 --- a/packages/inula-intl/package.json +++ b/packages/inula-intl/package.json @@ -3,13 +3,13 @@ "version": "0.0.5", "description": "", "main": "build/intl.umd.js", - "type": "commonjs", + "type": "module", "types": "build/@types/index.d.ts", "scripts": { "demo-serve": "webpack serve --mode=development", - "build": "rollup --config rollup.config.js && npm run build-types", + "build": "rollup --config rollup.config.js && npm run build-types ", "build-types": "tsc -p tsconfig.json && rollup -c build-type.js", - "test": "jest --config jest.config.js", + "test": "jest --no-cache --config jest.config.js", "test-c": "jest --coverage" }, "repository": { @@ -17,8 +17,7 @@ "url": "" }, "files": [ - "build", - "README.md" + "/build" ], "keywords": [], "author": "", @@ -27,35 +26,23 @@ "openinula": ">=0.1.1" }, "devDependencies": { - "@babel/core": "7.21.3", - "@babel/preset-env": "^7.16.7", "@babel/preset-react": "^7.9.4", - "@babel/preset-typescript": "7.16.7", - "@rollup/plugin-babel": "^6.0.3", - "@rollup/plugin-node-resolve": "^7.1.3", "@rollup/plugin-typescript": "^11.0.0", - "rollup-plugin-dts": "^6.1.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", - "@types/node": "^16.18.27", "@types/react": "18.0.25", - "babel": "^6.23.0", - "babel-jest": "^29.5.0", "babel-loader": "^9.1.2", "html-webpack-plugin": "^5.5.1", - "jest": "29.3.1", "jest-environment-jsdom": "^29.5.0", "jsdom": "^21.1.1", - "prettier": "^2.8.7", - "rollup": "^2.0.0", + "react": "18.2.0-h3", + "react-dom": "18.2.0-h3", "rollup-plugin-livereload": "^2.0.5", "rollup-plugin-serve": "^1.1.0", - "rollup-plugin-terser": "^5.3.0", - "tslib": "^2.6.1", - "ts-jest": "29.0.3", + "rollup-plugin-visualizer": "^5.10.0", "ts-node": "10.9.1", - "typescript": "4.9.3", - "webpack": "^5.81.0", + "tslib": "^2.6.1", + "webpack": "^5.72.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.13.3" } diff --git a/packages/inula-intl/report.html b/packages/inula-intl/report.html new file mode 100644 index 00000000..7bfe3ecf --- /dev/null +++ b/packages/inula-intl/report.html @@ -0,0 +1,4842 @@ + + + + + + + + Rollup Visualizer + + + +
+ + + + + diff --git a/packages/inula-intl/rollup.config.js b/packages/inula-intl/rollup.config.js index 86cd6fb2..6e76aad6 100644 --- a/packages/inula-intl/rollup.config.js +++ b/packages/inula-intl/rollup.config.js @@ -19,6 +19,7 @@ import babel from '@rollup/plugin-babel'; import nodeResolve from '@rollup/plugin-node-resolve'; import typescript from '@rollup/plugin-typescript'; import { terser } from 'rollup-plugin-terser'; +import { visualizer } from 'rollup-plugin-visualizer'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -29,34 +30,55 @@ const output = path.join(__dirname, '/build'); const extensions = ['.js', '.ts', '.tsx']; -export default { - input: entry, - output: [ +const BuildConfig = mode => { + const prod = mode.startsWith('prod'); + const outputList = [ { - file: path.resolve(output, 'intl.umd.js'), - name: 'InulaI18n', - format: 'umd', + file: path.join(output, `cjs/intl.${prod ? 'min.' : ''}js`), + sourcemap: 'true', + format: 'cjs', + globals: { + openinula: 'Inula', + }, }, { - file: path.resolve(output, 'intl.esm-browser.js'), + file: path.join(output, `umd/intl.${prod ? 'min.' : ''}js`), + name: 'InulaI18n', + sourcemap: 'true', + format: 'umd', + globals: { + openinula: 'Inula', + }, + }, + ]; + if (!prod) { + outputList.push({ + file: path.join(output, 'esm/intl.js'), + sourcemap: 'true', format: 'esm', - } - ], - plugins: [ - nodeResolve({ - extensions, - modulesOnly: true, - }), - babel({ - exclude: 'node_modules/**', - configFile: path.join(__dirname, '/babel.config.js'), - extensions, - }), - typescript({ - tsconfig: 'tsconfig.json', - include: ['./**/*.ts', './**/*.tsx'], - }), - terser(), - ], - external: ['openinula', 'react', 'react-dom'], + }); + } + return { + input: entry, + output: outputList, + plugins: [ + nodeResolve({ + extensions, + modulesOnly: true, + }), + babel({ + exclude: 'node_modules/**', + configFile: path.join(__dirname, '/.babelrc'), + extensions, + babelHelpers: 'runtime', + }), + typescript({ + tsconfig: 'tsconfig.json', + include: ['./**/*.ts', './**/*.tsx'], + }), + terser(), + ], + external: ['openinula', 'react', 'react-dom'], + }; }; +export default [BuildConfig('dev'), BuildConfig('prod')]; diff --git a/packages/inula-intl/src/constants/index.ts b/packages/inula-intl/src/constants/index.ts index c33b5127..f676cee3 100644 --- a/packages/inula-intl/src/constants/index.ts +++ b/packages/inula-intl/src/constants/index.ts @@ -18,8 +18,13 @@ * \\x[a-fA-F0-9]{2} 匹配形如 \x0A 的十六进制转义字符。 * [nrtf'"] 匹配常见的转义字符:\n(换行符)、\r(回车符)、\t(制表符)、\f(换页符)、\'(单引号)和 \"(双引号)。 */ -export const UNICODE_REG = /\\(?:u\{[a-fA-F0-9]+}|x[a-fA-F0-9]{2}|[nrtf'"])/g; +export const UNICODE_REG: RegExp = /\\(?:u\{[a-fA-F0-9]+}|x[a-fA-F0-9]{2}|[nrtf'"])/g; +export const STICKY_FLAG: string = 'ym'; +export const GLOBAL_FLAG: string = 'gm'; +export const VERTICAL_LINE: string = '|'; +export const UNICODE_FLAG: string = 'u'; +export const STATE_GROUP_START_INDEX: number = 1; // Inula 需要被保留静态常量 export const INULA_STATICS = { childContextTypes: true, @@ -76,3 +81,22 @@ export const INULA_MEMO_STATICS = { // 默认复数规则 export const DEFAULT_PLURAL_KEYS = ['zero', 'one', 'two', 'few', 'many', 'other']; + +export const voidElementTags = [ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'keygen', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr', + 'menuitem', +]; diff --git a/packages/inula-intl/src/core/I18n.ts b/packages/inula-intl/src/core/I18n.ts index 337166d1..503f080d 100644 --- a/packages/inula-intl/src/core/I18n.ts +++ b/packages/inula-intl/src/core/I18n.ts @@ -18,30 +18,48 @@ import DateTimeFormatter from '../format/fomatters/DateTimeFormatter'; import NumberFormatter from '../format/fomatters/NumberFormatter'; import { getFormatMessage } from '../format/getFormatMessage'; import { I18nCache, I18nProps, MessageDescriptor, MessageOptions } from '../types/interfaces'; -import { Locale, Locales, Messages, AllLocaleConfig, AllMessages, LocaleConfig, Error, Events } from '../types/types'; +import { + Locale, + Locales, + Messages, + AllLocaleConfig, + AllMessages, + LocaleConfig, + Error, + Events, + InulaNode, +} from '../types/types'; import creatI18nCache from '../format/cache/cache'; +import { isValidElement } from 'openinula'; export class I18n extends EventDispatcher { public locale: Locale; public locales: Locales; + public defaultLocale?: Locale; + public timeZone?: string; + private allMessages: AllMessages; private readonly _localeConfig: AllLocaleConfig; - private readonly allMessages: AllMessages; - public readonly error?: Error; + public readonly onError?: Error; public readonly cache?: I18nCache; constructor(props: I18nProps) { super(); - this.locale = 'en'; + this.defaultLocale = 'en'; + this.locale = this.defaultLocale; this.locales = this.locale || ''; this.allMessages = {}; this._localeConfig = {}; - this.error = props.error; + this.onError = props.onError; + this.timeZone = ''; this.loadMessage(props.messages); if (props.localeConfig) { this.loadLocaleConfig(props.localeConfig); } + if (props.messages) { + this.changeMessage(props.messages); + } if (props.locale || props.locales) { this.changeLanguage(props.locale!, props.locales); @@ -93,6 +111,11 @@ export class I18n extends EventDispatcher { } } + changeMessage(messages: AllMessages) { + this.allMessages = messages; + this.emit('change'); + } + // 加载messages loadMessage(localeOrMessages: Locale | AllMessages | undefined, messages?: Messages) { if (messages) { @@ -118,9 +141,21 @@ export class I18n extends EventDispatcher { formatMessage( id: MessageDescriptor | string, values: Record | undefined = {}, - { message, context, formatOptions }: MessageOptions = {} + { messages, context, formatOptions }: MessageOptions = {} ) { - return getFormatMessage(this, id, values, { message, context, formatOptions }); + // 在多次渲染时,保证存储component不丢失 + const components: { [key: string]: InulaNode } = {}; + const tempValues: Record = { ...values }; + if (tempValues) { + Object.keys(tempValues).forEach((key, index) => { + const value = tempValues[key]; + if (!isValidElement(value)) return; + // 将inula元素暂存 + components[index] = value; + tempValues[key] = `<${index}/>`; + }); + } + return getFormatMessage(this, id, tempValues, { messages, context, formatOptions }, components!); } formatDate(value: string | Date, formatOptions?: Intl.DateTimeFormatOptions): string { diff --git a/packages/inula-intl/src/core/components/FormattedMessage.tsx b/packages/inula-intl/src/core/components/FormattedMessage.tsx index f2a03e0a..c91464d2 100644 --- a/packages/inula-intl/src/core/components/FormattedMessage.tsx +++ b/packages/inula-intl/src/core/components/FormattedMessage.tsx @@ -12,7 +12,7 @@ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. */ -import Inula, { Children, Fragment } from 'openinula'; +import { Children, Fragment } from 'openinula'; import { FormattedMessageProps } from '../../types/interfaces'; import useI18n from '../hook/useI18n'; @@ -22,28 +22,17 @@ import useI18n from '../hook/useI18n'; * @constructor */ function FormattedMessage(props: FormattedMessageProps) { - const { i18n } = useI18n(); - const { - id, - values, - messages, - formatOptions, - context, - tagName: TagName = Fragment, - children, - comment, - useMemorize, - }: any = props; + const { formatMessage } = useI18n(); + const { id, values, messages, formatOptions, context, tagName: TagName = Fragment, children, comment }: any = props; const formatMessageOptions = { comment, messages, context, - useMemorize, formatOptions, }; - let formattedMessage = i18n.formatMessage(id, values, formatMessageOptions); + const formattedMessage = formatMessage(id, values, formatMessageOptions); if (typeof children === 'function') { const childNodes = Array.isArray(formattedMessage) ? formattedMessage : [formattedMessage]; diff --git a/packages/inula-intl/src/core/components/I18nProvider.tsx b/packages/inula-intl/src/core/components/I18nProvider.tsx index 18d8f896..3ef7e203 100644 --- a/packages/inula-intl/src/core/components/I18nProvider.tsx +++ b/packages/inula-intl/src/core/components/I18nProvider.tsx @@ -12,10 +12,10 @@ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. */ -import Inula, { useRef, useState, useEffect, useMemo } from 'openinula'; +import { useRef, useState, useEffect, useMemo } from 'openinula'; import { InjectProvider } from './InjectI18n'; import I18n, { createI18nInstance } from '../I18n'; -import { I18nProviderProps } from '../../types/types'; +import { AllMessages, I18nProviderProps, Messages } from '../../types/types'; /** * 用于为应用程序提供国际化的格式化功能,管理程序中的语言文本信息和本地化资源信息 @@ -23,28 +23,31 @@ import { I18nProviderProps } from '../../types/types'; * @constructor */ const I18nProvider = (props: I18nProviderProps) => { - const { locale, messages, children } = props; + const { locale, messages, children, i18n } = props; - const i18n = useMemo(() => { - return createI18nInstance({ - locale: locale, - messages: messages, - }); - }, [locale, messages]); + const i18nInstance = + i18n || + useMemo(() => { + return createI18nInstance({ + locale: locale, + messages: messages, + }); + }, [locale, messages]); // 使用useRef保存上次的locale值 - const localeRef = useRef(i18n.locale); - - const [context, setContext] = useState(i18n); + const localeRef = useRef(i18nInstance.locale); + const localeMessage = useRef(i18nInstance.messages); + const [context, setContext] = useState(i18nInstance); useEffect(() => { const handleChange = () => { - if (localeRef.current !== i18n.locale) { - localeRef.current = i18n.locale; - setContext(i18n); + if (localeRef.current !== i18nInstance.locale || localeMessage.current !== i18nInstance.messages) { + localeRef.current = i18nInstance.locale; + localeMessage.current = i18nInstance.messages; + setContext(i18nInstance); } }; - let removeListener = i18n.on('change', handleChange); + const removeListener = i18nInstance.on('change', handleChange); // 手动触发一次 handleChange,以确保 context 的正确性 handleChange(); @@ -53,7 +56,7 @@ const I18nProvider = (props: I18nProviderProps) => { return () => { removeListener(); }; - }, [i18n]); + }, [i18nInstance]); // 提供一个Provider组件 return {children}; diff --git a/packages/inula-intl/src/core/components/InjectI18n.tsx b/packages/inula-intl/src/core/components/InjectI18n.tsx index 82589098..20436609 100644 --- a/packages/inula-intl/src/core/components/InjectI18n.tsx +++ b/packages/inula-intl/src/core/components/InjectI18n.tsx @@ -31,13 +31,16 @@ export const InjectProvider = Provider; function injectI18n(Component, options?: InjectOptions): any { const { isUsingForwardRef = false, // 默认不使用 + ensureContext = false, } = options || {}; // 定义一个名为 WrappedI18n 的函数组件,接收传入组件的 props 和 forwardedRef,返回传入组件并注入 i18n const WrappedI18n = props => ( {context => { - isVariantI18n(context); + if (ensureContext) { + isVariantI18n(context); + } const i18nProps = { intl: context, diff --git a/packages/inula-intl/src/core/createI18n.ts b/packages/inula-intl/src/core/createI18n.ts index 4629a8fc..ab41b67b 100644 --- a/packages/inula-intl/src/core/createI18n.ts +++ b/packages/inula-intl/src/core/createI18n.ts @@ -13,20 +13,29 @@ * See the Mulan PSL v2 for more details. */ import { configProps, I18nCache } from '../types/interfaces'; -import I18n, { createI18nInstance } from './I18n'; +import { createI18nInstance } from './I18n'; import creatI18nCache from '../format/cache/cache'; +import { IntlType } from '../types/types'; /** * createI18n hook函数,用于创建国际化i8n实例,以进行相关的数据操作 */ -export const createI18n = (config: configProps, cache?: I18nCache): I18n => { +export const createI18n = (config: configProps, cache?: I18nCache): IntlType => { const { locale, defaultLocale, messages } = config; - return createI18nInstance({ - locale: locale || defaultLocale || 'zh', + const i18n = createI18nInstance({ + locale: locale || defaultLocale || 'en', messages: messages, cache: cache ?? creatI18nCache(), }); + return { + i18n, + ...config, + formatMessage: i18n.formatMessage.bind(i18n), + formatNumber: i18n.formatNumber.bind(i18n), + formatDate: i18n.formatDate.bind(i18n), + $t: i18n.formatMessage.bind(i18n), + }; }; export default createI18n; diff --git a/packages/inula-intl/src/core/hook/useI18n.ts b/packages/inula-intl/src/core/hook/useI18n.ts index ae0b7f2d..ffcf709f 100644 --- a/packages/inula-intl/src/core/hook/useI18n.ts +++ b/packages/inula-intl/src/core/hook/useI18n.ts @@ -12,7 +12,7 @@ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. */ -import Inula, { useContext } from 'openinula'; +import { useContext, useMemo } from 'openinula'; import utils from '../../utils/utils'; import { I18nContext } from '../components/InjectI18n'; import I18n from '../I18n'; @@ -23,15 +23,22 @@ import { IntlType } from '../../types/types'; * 使用 useI18n 钩子函数可以更方便地在函数组件中进行国际化操作 */ function useI18n(): IntlType { - const i18nContext = useContext(I18nContext); - utils.isVariantI18n(i18nContext); - const i18n = i18nContext; - return { - i18n: i18n, - formatMessage: i18n.formatMessage.bind(i18n), - formatNumber: i18n.formatNumber.bind(i18n), - formatDate: i18n.formatDate.bind(i18n), - }; + const i18n = useContext(I18nContext); + utils.isVariantI18n(i18n); + return useMemo(() => { + return { + i18n: i18n, + locale: i18n.locale, + messages: i18n.messages, + defaultLocale: i18n.defaultLocale, + timeZone: i18n.timeZone, + onError: i18n.onError, + formatMessage: i18n.formatMessage.bind(i18n), + formatNumber: i18n.formatNumber.bind(i18n), + formatDate: i18n.formatDate.bind(i18n), + $t: i18n.formatMessage.bind(i18n), + }; + }, [i18n]); } export default useI18n; diff --git a/packages/inula-intl/src/format/Translation.ts b/packages/inula-intl/src/format/Translation.ts index 20f27b77..bf7830c9 100644 --- a/packages/inula-intl/src/format/Translation.ts +++ b/packages/inula-intl/src/format/Translation.ts @@ -16,7 +16,7 @@ import { CompiledMessage, Locale, LocaleConfig, Locales } from '../types/types'; import generateFormatters from './generateFormatters'; import { FormatOptions, I18nCache } from '../types/interfaces'; -import { createIntlCache } from '../../index'; +import creatI18nCache from './cache/cache'; /** * 获取翻译结果 @@ -28,12 +28,18 @@ class Translation { private readonly localeConfig: Record; private readonly cache: I18nCache; - constructor(compiledMessage, locale, locales, localeConfig, cache?) { + constructor( + compiledMessage: CompiledMessage, + locale: Locale, + locales: Locales, + localeConfig: LocaleConfig, + cache?: I18nCache + ) { this.compiledMessage = compiledMessage; this.locale = locale; this.locales = locales; this.localeConfig = localeConfig; - this.cache = cache ?? createIntlCache; + this.cache = cache ?? creatI18nCache(); } /** @@ -53,7 +59,7 @@ class Translation { const value = values[name]; const formatter = formatters[type](value, format); - let message; + let message: any; if (typeof formatter === 'function') { message = formatter(textFormatter); // 递归调用 } else { @@ -68,8 +74,7 @@ class Translation { const textFormatter = createTextFormatter(this.locale, this.locales, values, formatOptions, this.localeConfig); // 通过递归方法formatCore进行格式化处理 - const result = this.formatMessage(this.compiledMessage, textFormatter); - return result; // 返回要格式化的结果 + return this.formatMessage(this.compiledMessage, textFormatter); // 返回要格式化的结果 } formatMessage(compiledMessage: CompiledMessage, textFormatter: (...args: any[]) => any) { diff --git a/packages/inula-intl/src/format/fomatters/PluralFormatter.ts b/packages/inula-intl/src/format/fomatters/PluralFormatter.ts index c0db6c9e..d149ae3f 100644 --- a/packages/inula-intl/src/format/fomatters/PluralFormatter.ts +++ b/packages/inula-intl/src/format/fomatters/PluralFormatter.ts @@ -17,7 +17,7 @@ import utils from '../../utils/utils'; import NumberFormatter from './NumberFormatter'; import { Locale, Locales } from '../../types/types'; import { I18nCache } from '../../types/interfaces'; -import { createIntlCache } from '../../../index'; +import creatI18nCache from '../cache/cache'; /** * 复数格式化 @@ -29,12 +29,12 @@ class PluralFormatter { private readonly message: any; private readonly cache: I18nCache; - constructor(locale, locales, value, message, cache?) { + constructor(locale: Locale, locales: Locales, value: any, message: any, cache?:I18nCache) { this.locale = locale; this.locales = locales; this.value = value; this.message = message; - this.cache = cache ?? createIntlCache(); + this.cache = cache ?? creatI18nCache(); } // 将 message中的“#”替换为指定数字value,并返回新的字符串或者字符串数组 diff --git a/packages/inula-intl/src/format/fomatters/SelectFormatter.ts b/packages/inula-intl/src/format/fomatters/SelectFormatter.ts index 1ff3e5b6..f61ee0af 100644 --- a/packages/inula-intl/src/format/fomatters/SelectFormatter.ts +++ b/packages/inula-intl/src/format/fomatters/SelectFormatter.ts @@ -14,7 +14,7 @@ */ import utils from '../../utils/utils'; -import { Locale } from '../../types/types'; +import {Locale, SelectPool} from '../../types/types'; import { I18nCache } from '../../types/interfaces'; /** @@ -26,12 +26,12 @@ class SelectFormatter { private readonly locale: Locale; private readonly cache: I18nCache; - constructor(locale, cache) { + constructor(locale: Locale, cache: I18nCache) { this.locale = locale; this.cache = cache; } - getRule(value, rules) { + getRule(value: SelectPool, rules: any) { if (this.cache.select) { // 创建key,用于唯一标识 const cacheKey = utils.generateKey(this.locale, rules); diff --git a/packages/inula-intl/src/format/generateFormatters.ts b/packages/inula-intl/src/format/generateFormatters.ts index 567b9f9b..92c58693 100644 --- a/packages/inula-intl/src/format/generateFormatters.ts +++ b/packages/inula-intl/src/format/generateFormatters.ts @@ -19,25 +19,23 @@ import { DatePool, Locale, Locales, SelectPool } from '../types/types'; import PluralFormatter from './fomatters/PluralFormatter'; import SelectFormatter from './fomatters/SelectFormatter'; import { FormatOptions, I18nCache, IntlMessageFormat } from '../types/interfaces'; -import cache from './cache/cache'; /** * 默认格式化接口 */ const generateFormatters = ( - locale: Locale | Locales, + locale: Locale, locales: Locales, localeConfig: Record = { plurals: undefined }, formatOptions: FormatOptions = {}, // 自定义格式对象 cache: I18nCache ): IntlMessageFormat => { - locale = locales || locale; const { plurals } = localeConfig; /** * 样式函数 ,根据格式获取格式样式, 如货币百分比, 返回相应的格式的对象,如果没有设定格式,则返回一个空对象 * @param formatOption */ - const getStyleOption = formatOption => { + const getStyleOption = (formatOption: string | number) => { if (typeof formatOption === 'string') { return formatOptions[formatOption] || { option: formatOption }; } else { @@ -58,14 +56,14 @@ const generateFormatters = ( return pluralFormatter.replaceSymbol.bind(pluralFormatter); }, - selectordinal: (value: number, { offset = 0, ...rules }, useMemorize?) => { + selectordinal: (value: number, { offset = 0, ...rules }) => { const message = rules[value] || rules[(plurals as any)?.(value - offset, true)] || rules.other; - const pluralFormatter = new PluralFormatter(locale, locales, value - offset, message, useMemorize); + const pluralFormatter = new PluralFormatter(locale, locales, value - offset, message, cache); return pluralFormatter.replaceSymbol.bind(pluralFormatter); }, // 选择规则,如果规则对象中包含与该值相对应的属性,则返回该属性的值;否则,返回 "other" 属性的值。 - select: (value: SelectPool, formatRules) => { + select: (value: SelectPool, formatRules: any) => { const selectFormatter = new SelectFormatter(locale, cache); return selectFormatter.getRule(value, formatRules); }, @@ -75,17 +73,16 @@ const generateFormatters = ( return new NumberFormatter(locales, getStyleOption(formatOption), cache).numberFormat(value); }, - // 用于将日期格式化为字符串,接受一个日期对象和一个格式化规则。它会根据规则返回格式化后的字符串。 /** + * 用于将日期格式化为字符串,接受一个日期对象和一个格式化规则。它会根据规则返回格式化后的字符串。 * eg: { year: 'numeric', month: 'long', day: 'numeric' } 是一个用于指定DateTimeFormatter如何将日期对象转换为字符串的参数。 * \year: 'numeric' 表示年份的表示方式是数字形式(比如2023)。 * month: 'long' 表示月份的表示方式是全名(比如January)。 * day: 'numeric' 表示日期的表示方式是数字形式(比如1号)。 * @param value * @param formatOption { year: 'numeric', month: 'long', day: 'numeric' } - * @param useMemorize */ - dateTimeFormat: (value: DatePool, formatOption) => { + dateTimeFormat: (value: DatePool, formatOption: any) => { return new DateTimeFormatter(locales, getStyleOption(formatOption), cache).dateTimeFormat(value, formatOption); }, diff --git a/packages/inula-intl/src/format/getFormatMessage.ts b/packages/inula-intl/src/format/getFormatMessage.ts index 6e34b6f5..cc55eb08 100644 --- a/packages/inula-intl/src/format/getFormatMessage.ts +++ b/packages/inula-intl/src/format/getFormatMessage.ts @@ -19,19 +19,21 @@ import I18n from '../core/I18n'; import { MessageDescriptor, MessageOptions } from '../types/interfaces'; import { CompiledMessage } from '../types/types'; import creatI18nCache from './cache/cache'; +import { formatElements } from '../utils/formatElements'; export function getFormatMessage( i18n: I18n, id: MessageDescriptor | string, values: Record | undefined = {}, - options: MessageOptions = {} + options: MessageOptions = {}, + components: any ) { - let { message, context } = options; + let { messages, context } = options; const { formatOptions } = options; const cache = i18n.cache ?? creatI18nCache(); if (typeof id !== 'string') { values = values || id.defaultValues; - message = id.message || id.defaultMessage; + messages = id.messages || id.defaultMessage; context = id.context; id = id.id; } @@ -42,7 +44,7 @@ export function getFormatMessage( const messageUnavailable = isMissingContextMessage || isMissingMessage; // 对错误消息进行处理 - const messageError = i18n.error; + const messageError = i18n.onError; if (messageError && messageUnavailable) { if (typeof messageError === 'function') { return messageError(i18n.locale, id, context); @@ -53,14 +55,17 @@ export function getFormatMessage( let compliedMessage: CompiledMessage; if (context) { - compliedMessage = i18n.messages[context][id] || message || id; + compliedMessage = i18n.messages[context][id] || messages || id; } else { - compliedMessage = i18n.messages[id] || message || id; + compliedMessage = i18n.messages[id] || messages || id; } - // 对解析的messages进行parse解析,并输出解析后的Token + // 对解析的message进行parse解析,并输出解析后的Token compliedMessage = typeof compliedMessage === 'string' ? utils.compile(compliedMessage) : compliedMessage; const translation = new Translation(compliedMessage, i18n.locale, i18n.locales, i18n.localeConfig, cache); - return translation.translate(values, formatOptions); + const formatResult = translation.translate(values, formatOptions); + + // 如果存在inula元素,则返回包含格式化的Inula元素的数组 + return formatElements(formatResult, components); } diff --git a/packages/inula-intl/src/parser/Lexer.ts b/packages/inula-intl/src/parser/Lexer.ts index eee91a80..4f23b752 100644 --- a/packages/inula-intl/src/parser/Lexer.ts +++ b/packages/inula-intl/src/parser/Lexer.ts @@ -16,9 +16,12 @@ import ruleUtils from '../utils/parseRuleUtils'; import { LexerInterface } from '../types/interfaces'; +/** + * 词法解析器,主要根据设计的规则对message进行处理成Token + */ class Lexer implements LexerInterface { readonly startState: string; - readonly states: Record; + readonly unionReg: Record; private buffer = ''; private stack: string[] = []; private index = 0; @@ -28,19 +31,23 @@ class Lexer implements LexerInterface { private state = ''; private groups: string[] = []; private error: Record | undefined; - private regexp; + private regexp: any; private fast: Record = {}; private queuedGroup: string | null = ''; private value = ''; constructor(unionReg: Record, startState: string) { this.startState = startState; - this.states = unionReg; + this.unionReg = unionReg; this.buffer = ''; this.stack = []; this.reset(); } + /** + * 根据新的消息重置解析器 + * @param data 消息数据 + */ public reset(data?: string) { this.buffer = data || ''; this.index = 0; @@ -57,7 +64,7 @@ class Lexer implements LexerInterface { return; } this.state = state; - const info = this.states[state]; + const info = this.unionReg[state]; this.groups = info.groups; this.error = info.error; this.regexp = info.regexp; @@ -73,7 +80,7 @@ class Lexer implements LexerInterface { this.setState(state); } - private getGroup(match: Record) { + private getGroup(match: Record) { const groupCount = this.groups.length; for (let i = 0; i < groupCount; i++) { if (match[i + 1] !== undefined) { @@ -87,7 +94,9 @@ class Lexer implements LexerInterface { return this.value; } - // 迭代获取下一个 token + /** + * 迭代获取下一个 token + */ public next() { const index = this.index; @@ -112,7 +121,6 @@ class Lexer implements LexerInterface { const regexp = this.regexp; regexp.lastIndex = index; const match = getMatch(regexp, buffer); - const error = this.error; if (match == null) { return this.getToken(error, buffer.slice(index, buffer.length), index); @@ -131,9 +139,9 @@ class Lexer implements LexerInterface { } /** - * 獲取Token - * @param group 解析模板后獲得的屬性值 - * @param text 文本屬性的信息 + * 获取Token + * @param group 解析模板后获得的属性值 + * @param text 文本属性的信息 * @param offset 偏移量 * @private */ @@ -187,7 +195,7 @@ class Lexer implements LexerInterface { return token; } - // 增加迭代器 + // 增加迭代器,允许逐个访问集合中的元素方法 [Symbol.iterator]() { return { next: (): IteratorResult => { @@ -198,9 +206,15 @@ class Lexer implements LexerInterface { } } +/** + * 根据正则表达式,获取匹配到message的值 + * 索引为 0 的元素是完整的匹配结果。 + * 索引为 1、2、3 等的元素是正则表达式中指定的捕获组的匹配结果。 + */ const getMatch = ruleUtils.checkSticky() ? // 正则表达式具有 sticky 标志 - (regexp, buffer) => regexp.exec(buffer) + (regexp: any, buffer: string) => regexp.exec(buffer) : // 正则表达式具有 global 标志,匹配的字符串长度为 0,则表示匹配失败 - (regexp, buffer) => (regexp.exec(buffer)[0].length === 0 ? null : regexp.exec(buffer)); + (regexp: any, buffer: string) => (regexp.exec(buffer)[0].length === 0 ? null : regexp.exec(buffer)); + export default Lexer; diff --git a/packages/inula-intl/src/parser/mappingRule.ts b/packages/inula-intl/src/parser/mappingRule.ts index 1dff32ec..d11824ce 100644 --- a/packages/inula-intl/src/parser/mappingRule.ts +++ b/packages/inula-intl/src/parser/mappingRule.ts @@ -14,40 +14,47 @@ */ const body: Record = { - doubleapos: { match: '\'\'', value: () => '\'' }, + doubleapos: { match: "''", value: () => "'" }, quoted: { lineBreaks: true, - match: /'[{}#](?:[^]*?[^'])?'(?!')/u, - value: src => src.slice(1, -1).replace(/''/g, '\''), + match: /'[{}#](?:[^]*?[^'])?'(?!')/u, // 用以匹配单引号、花括号{}以及井号# 如'Hello' 、{name}、{}# + value: (src: string) => src.slice(1, -1).replace(/''/g, "'"), }, argument: { lineBreaks: true, + + // 用于匹配{name、{Hello{World,匹配{ }花括号中有任何Unicode字符,如空格、制表符等 match: /\{\s*[^\p{Pat_Syn}\p{Pat_WS}]+\s*/u, push: 'arg', - value: src => src.substring(1).trim(), + value: (src: string) => src.substring(1).trim(), }, octothorpe: '#', end: { match: '}', pop: 1 }, - content: { lineBreaks: true, match: /[^][^{}#']*/u }, + content: { + lineBreaks: true, + match: /[^][^{}#]*/u, // 主要匹配不包含[]任何字符(除了换行符)、不包含{}、#的任何个字符 + }, }; const arg: Record = { select: { lineBreaks: true, - match: /,\s*(?:plural|select|selectordinal)\s*,\s*/u, - next: 'select', - value: src => src.split(',')[1].trim(), + match: /,\s*(?:plural|select|selectordinal)\s*,\s*/u, // 匹配内容包含 plural、select 或 selectordinal + next: 'select', // 继续解析下一个参数 + value: (src: string) => src.split(',')[1].trim(), // 提取第二个参数,并处理收尾空格 }, 'func-args': { + // 匹配是否包含其他非特殊字符的参数,匹配结果包含特殊字符,如param1, param2, param3 lineBreaks: true, match: /,\s*[^\p{Pat_Syn}\p{Pat_WS}]+\s*,/u, next: 'body', - value: src => src.split(',')[1].trim(), + value: (src: string) => src.split(',')[1].trim(), // 参数字符串去除逗号并去除首尾空格 }, 'func-simple': { + // 匹配是否包含其他简单参数,匹配结果不包含标点符号:param1 param2 param3 lineBreaks: true, match: /,\s*[^\p{Pat_Syn}\p{Pat_WS}]+\s*/u, - value: src => src.substring(1).trim(), + value: (src: string) => src.substring(1).trim(), }, end: { match: '}', pop: 1 }, }; @@ -55,14 +62,17 @@ const arg: Record = { const select: Record = { offset: { lineBreaks: true, - match: /\s*offset\s*:\s*\d+\s*/u, - value: src => src.split(':')[1].trim(), + match: /\s*offset\s*:\s*\d+\s*/u, // 匹配message中是否包含偏移量offest信息 + value: (src: string) => src.split(':')[1].trim(), }, case: { + // 检查匹配该行是否包含分支信息。 lineBreaks: true, + + // 设置规则匹配以左大括号 { 结尾的字符串,以等号 = 后跟数字开头的字符串,或者以非特殊符号和非空白字符开头的字符串,如 '=1 {' match: /\s*(?:=\d+|[^\p{Pat_Syn}\p{Pat_WS}]+)\s*\{/u, - push: 'body', - value: src => src.substring(0, src.indexOf('{')).trim(), + push: 'body', // 匹配成功,则会push到body栈中 + value: (src: string) => src.substring(0, src.indexOf('{')).trim(), }, end: { match: /\s*\}/u, pop: 1 }, }; diff --git a/packages/inula-intl/src/parser/parseMappingRule.ts b/packages/inula-intl/src/parser/parseMappingRule.ts index 5cdaf6f0..4ea561c9 100644 --- a/packages/inula-intl/src/parser/parseMappingRule.ts +++ b/packages/inula-intl/src/parser/parseMappingRule.ts @@ -17,12 +17,13 @@ import Lexer from './Lexer'; import { mappingRule } from './mappingRule'; import ruleUtils from '../utils/parseRuleUtils'; import { RawToken } from '../types/types'; +import { STATE_GROUP_START_INDEX, GLOBAL_FLAG, STICKY_FLAG, UNICODE_FLAG, VERTICAL_LINE } from '../constants'; const defaultErrorRule = ruleUtils.getRuleOptions('error', { lineBreaks: true, shouldThrow: true }); // 解析规则并生成词法分析器所需的数据结构,以便进行词法分析操作 -function parseRules(rules: Record, hasStates: boolean): Record { - let errorRule: Record | null = null; +function parseRules(rules: Record, hasStates: boolean): Record { + let errorRule: Record | null = null; const fast: Record = {}; let enableFast = true; let unicodeFlag: boolean | null = null; @@ -58,7 +59,7 @@ function parseRules(rules: Record, hasStates: boolean): Record, hasStates: boolean): Record, hasStates: boolean): Record, name: string, map: Record) { +export function checkStateGroup(group: Record, name: string, mappingRules: Record) { const state = group && (group.push || group.next); - if (state && !map[state]) { + if (state && !mappingRules[state]) { throw new Error('The state is missing.'); } - if (group && group.pop && +group.pop !== 1) { + if (group && group.pop && +group.pop !== STATE_GROUP_START_INDEX) { throw new Error('The value of pop must be 1.'); } } // 将国际化解析规则注入分词器中 -function parseMappingRule(mappingRule: Record, startState?: string): Lexer { +function parseMappingRule(mappingRule: Record, startState?: string): Lexer { const keys = Object.getOwnPropertyNames(mappingRule); if (!startState) { @@ -133,7 +134,7 @@ function parseMappingRule(mappingRule: Record, startState?: string) continue; } - const splice = [j, 1]; + const splice = [j, STATE_GROUP_START_INDEX]; if (rule.include !== key && !included[rule.include]) { included[rule.include] = true; const newRules = ruleMap[rule.include]; @@ -174,17 +175,30 @@ function parseMappingRule(mappingRule: Record, startState?: string) }); }); + // 将规则注入到词法解析器 return new Lexer(mappingAllRules, startState); } -function processFast(match, fast: Record, options) { +/** + * 快速匹配模式 + * @param match + * @param fast + * @param options + */ +function processFast(match: Record, fast: Record = {}, options: Record) { while (match.length && typeof match[0] === 'string' && match[0].length === 1) { + // 获取到数组的第一个元素 const word = match.shift(); fast[word.charCodeAt(0)] = options; } } -function handleErrorRule(options, errorRule: Record) { +/** + * 用以处理错误逻辑 + * @param options 操作属性 + * @param errorRule 错误规则 + */ +function handleErrorRule(options: Record, errorRule: Record) { if (!options.fallback === !errorRule.fallback) { throw new Error('errorRule can only set one!'); } else { @@ -192,7 +206,13 @@ function handleErrorRule(options, errorRule: Record) { } } -function checkUnicode(match, unicodeFlag, options) { +/** + * 用以检查message中是否包含Unicode + * @param match 匹配到的message + * @param unicodeFlag Unicode标志 + * @param options 操作属性 + */ +function checkUnicode(match: Record, unicodeFlag: boolean | null, options: Record) { for (let j = 0; j < match.length; j++) { const obj = match[j]; if (!ruleUtils.checkRegExp(obj)) { @@ -201,14 +221,16 @@ function checkUnicode(match, unicodeFlag, options) { if (unicodeFlag === null) { unicodeFlag = obj.unicode; - } else if (unicodeFlag !== obj.unicode && options.fallback === false) { - throw new Error('If the /u flag is used, all!'); + } else { + if (unicodeFlag !== obj.unicode && options.fallback === false) { + throw new Error('If the /u flag is used, all!'); + } } } return unicodeFlag; } -function checkStateOptions(hasStates: boolean, options) { +function checkStateOptions(hasStates: boolean, options: Record) { if (!hasStates) { throw new Error('State toggle options are not allowed in stateless tokenizers!'); } @@ -217,6 +239,11 @@ function checkStateOptions(hasStates: boolean, options) { } } +/** + * 检查是否存在fallback属性,用以来判断快速匹配规则 + * @param rules + * @param enableFast + */ function isExistsFallback(rules: Record, enableFast: boolean) { for (let i = 0; i < rules.length; i++) { if (rules[i].fallback) { @@ -226,7 +253,7 @@ function isExistsFallback(rules: Record, enableFast: boolean) { return enableFast; } -function isOptionsErrorOrFallback(options, errorRule: Record | null) { +function isOptionsErrorOrFallback(options: Record, errorRule: Record | null) { if (options.error || options.fallback) { // 只能设置一个 errorRule if (errorRule) { diff --git a/packages/inula-intl/src/parser/parser.ts b/packages/inula-intl/src/parser/parser.ts index 221a68e3..c607ae86 100644 --- a/packages/inula-intl/src/parser/parser.ts +++ b/packages/inula-intl/src/parser/parser.ts @@ -14,23 +14,13 @@ */ import { lexer } from './parseMappingRule'; -import { RawToken, Token } from '../types/types'; +import { RawToken } from '../types/types'; import { DEFAULT_PLURAL_KEYS } from '../constants'; import { Content, FunctionArg, PlainArg, Select, TokenContext } from '../types/interfaces'; -import Lexer from './Lexer'; - -const getContext = (lt: Record): TokenContext => ({ - offset: lt.offset, - line: lt.line, - col: lt.col, - text: lt.text, - lineNum: lt.lineBreaks, -}); - -export const checkSelectType = (value: string): boolean => { - return value === 'plural' || value === 'select' || value === 'selectordinal'; -}; +/** + * 语法解析器,根据Token,获得具备上下文的AST + */ class Parser { cardinalKeys: string[] = DEFAULT_PLURAL_KEYS; ordinalKeys: string[] = DEFAULT_PLURAL_KEYS; @@ -39,7 +29,7 @@ class Parser { lexer.reset(message); } - isSelectKeyValid(token: RawToken, type: Select['type'], value: string) { + isSelectKeyValid(type: Select['type'], value: string) { if (value[0] === '=') { if (type === 'select') { throw new Error('The key value of the select type is invalid.'); @@ -75,7 +65,7 @@ class Parser { break; } case 'case': { - this.isSelectKeyValid(token, type, token.value); + this.isSelectKeyValid(type, token.value); select.cases.push({ key: token.value.replace(/=/g, ''), tokens: this.parse(isPlural), @@ -94,6 +84,11 @@ class Parser { throw new Error('The message end position is invalid.'); } + /** + * 解析获得的Token + * @param token + * @param isPlural + */ parseToken(token: RawToken, isPlural: boolean): PlainArg | FunctionArg | Select { const context = getContext(token); const nextToken = lexer.next(); @@ -153,7 +148,12 @@ class Parser { } } - // 在根级别解析时,遇到结束符号即结束解析并返回结果;而在非根级别解析时,遇到结束符号会被视为不合法的结束位置,抛出错误 + /** + * 解析方法入口 + * 在根级别解析时,遇到结束符号即结束解析并返回结果;而在非根级别解析时,遇到结束符号会被视为不合法的结束位置,抛出错误 + * @param isPlural 标记复数 + * @param isRoot 标记根节点 + */ parse(isPlural: boolean, isRoot?: boolean): Array { const tokens: any[] = []; let content: string | Content | null = null; @@ -201,6 +201,23 @@ class Parser { } } +/** + * 获得 Token 的上下文 + * @param Token Token + */ +const getContext = (Token: RawToken): TokenContext => ({ + offset: Token.offset, + line: Token.line, + col: Token.col, + text: Token.text, + lineNum: Token.lineBreaks, +}); + +// 用以检查select规则中的类型 +export const checkSelectType = (value: string): boolean => { + return value === 'plural' || value === 'select' || value === 'selectordinal'; +}; + export default function parse(message: string): Array { const parser = new Parser(message); return parser.parse(false, true); diff --git a/packages/inula-intl/src/types/interfaces.ts b/packages/inula-intl/src/types/interfaces.ts index 34f9f8a7..8b90e4de 100644 --- a/packages/inula-intl/src/types/interfaces.ts +++ b/packages/inula-intl/src/types/interfaces.ts @@ -13,15 +13,25 @@ * See the Mulan PSL v2 for more details. */ -import { AllLocaleConfig, AllMessages, Locale, Locales, Error, DatePool, SelectPool, RawToken } from './types'; +import { + AllLocaleConfig, + AllMessages, + Locale, + Locales, + Error, + DatePool, + SelectPool, + RawToken, + InulaNode, +} from './types'; import I18n from '../core/I18n'; import Lexer from '../parser/Lexer'; +import { InulaElement, Key } from 'openinula'; // FormattedMessage的参数定义 export interface FormattedMessageProps extends MessageDescriptor { values?: Record; tagName?: string; - children?(nodes: any[]): any; } @@ -34,7 +44,7 @@ export interface MessageDescriptor extends MessageOptions { export interface MessageOptions { comment?: string; - message?: string; + messages?: string; context?: string; formatOptions?: FormatOptions; } @@ -48,15 +58,26 @@ export interface I18nCache { octothorpe: Record; } +export interface RichText { + components?: { [key: string]: InulaNode }; +} + +export interface InulaPortal extends InulaElement { + key: Key | null; + children: InulaNode; +} + // I18n类的传参 -export interface I18nProps { +export type I18nProps = RichText & { locale?: Locale; locales?: Locales; messages?: AllMessages; + defaultLocale?: string; + timeZone?: string; localeConfig?: AllLocaleConfig; cache?: I18nCache; - error?: Error; -} + onError?: Error; +}; // 消息格式化选项类型 export interface FormatOptions { @@ -74,16 +95,13 @@ export interface I18nContextProps { i18n?: I18n; } -export interface configProps { - locale?: Locale; - messages?: AllMessages; - defaultLocale?: string; +export type configProps = I18nProps & { RenderOnLocaleChange?: boolean; children?: any; onWarn?: Error; -} +}; -export interface IntlMessageFormat extends configProps, MessageOptions { +export interface IntlMessageFormat { plural: ( value: number, { @@ -204,7 +222,6 @@ export interface InjectedIntl { formatMessage( messageDescriptor: MessageDescriptor, values?: Record, - options?: MessageOptions, - useMemorize?: boolean - ): string; + options?: MessageOptions + ): string | any[]; } diff --git a/packages/inula-intl/src/types/types.ts b/packages/inula-intl/src/types/types.ts index 917d7f07..b8252879 100644 --- a/packages/inula-intl/src/types/types.ts +++ b/packages/inula-intl/src/types/types.ts @@ -23,16 +23,17 @@ import { I18nContextProps, configProps, InjectedIntl, + InulaPortal, } from './interfaces'; -import I18n from '../core/I18n'; +import { InulaElement } from 'openinula'; -export type Error = string | ((message, id, context) => string); +export type Error = string | ((message: any, id: any, context: any) => string); export type Locale = string; export type Locales = Locale | Locale[]; -export type LocaleConfig = { plurals?: (...arg: any) => any }; +export type LocaleConfig = { plurals?: (...args: any[]) => any }; export type AllLocaleConfig = Record; @@ -59,7 +60,7 @@ export type Token = Content | PlainArg | FunctionArg | Select | Octothorpe; export type DatePool = Date | string; -export type SelectPool = string | Record; +export type SelectPool = string | number; export type RawToken = { type: string; @@ -74,13 +75,23 @@ export type RawToken = { export type I18nProviderProps = I18nContextProps & configProps; -export type IntlType = { - i18n: I18n; +export type IntlType = I18nContextProps & { + defaultLocale?: string | undefined; + onError?: Error | undefined; + messages?: + | string + | Record + | Record + | Record | Record>; + locale?: string; formatMessage: (...args: any[]) => any; formatNumber: (...args: any[]) => any; formatDate: (...args: any[]) => any; + $t?: (...args: any[]) => any; }; -export interface InjectedIntlProps { +export type InjectedIntlProps = { intl: InjectedIntl; -} +}; + +export type InulaNode = InulaElement | string | number | Iterable | InulaPortal | boolean | null | undefined; diff --git a/packages/inula-intl/src/utils/formatElements.ts b/packages/inula-intl/src/utils/formatElements.ts new file mode 100644 index 00000000..fa3c77d3 --- /dev/null +++ b/packages/inula-intl/src/utils/formatElements.ts @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2024 Huawei Technologies Co.,Ltd. + * + * openInula is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { cloneElement, createElement, Fragment, InulaElement } from 'openinula'; +import { voidElementTags } from '../constants'; + +// 用于匹配标签的正则表达式 +const tagReg = /<(\d+)>(.*?)<\/\1>|<(\d+)\/>/; + +// 用于匹配换行符的正则表达式 +const nlReg = /(?:\r\n|\r|\n)/g; + +export function formatElements( + value: string, + elements: { [key: string]: InulaElement } = {} +): string | Array { + const elementKeyID = getElementIndex(0, '$Inula'); + + // value:This is a rich text with a custom component: <1/> + const arrays = value.replace(nlReg, '').split(tagReg); + + // 若无InulaNode元素,则返回 + if (arrays.length === 1) return value; + + const result: any = []; + + const before = arrays.shift(); + if (before) { + result.push(before); + } + + for (const [index, children, after] of getElements(arrays)) { + let element = elements[index]; + + if (!element || (voidElementTags[element.type as string] && children)) { + const errorMessage = !element + ? `Index not declared as ${index} in original translation` + : `${element.type} , No child element exists. Please check.`; + console.error(errorMessage); + + // 对于异常元素,通过创建<>来代替,并继续解析现有的子元素和之后的元素,并保证在构建数组时,不会因为缺少元素而导致索引错位。 + element = createElement(Fragment, {}); + } + + // 如果存在子元素,则进行递归处理 + const formattedChildren = children ? formatElements(children, elements) : element.props.children; + + // 更新element 的属性和子元素 + const clonedElement = cloneElement(element, { key: elementKeyID() }, formattedChildren); + result.push(clonedElement); + + if (after) { + result.push(after); + } + } + return result; +} + +/** + * 从arrays数组中解析出标签元素和其子元素 + * @param arrays + */ +function getElements(arrays: string[]) { + // 如果 arrays 数组为空,则返回空数组 + if (!arrays.length) return []; + + /** + * pairedIndex: 第一个元素表示配对标签的内容,即 <1>... 形式的标签。 + * children: 第二个元素表示配对标签内的子元素内容。 + * unpairedIndex: 第三个元素表示自闭合标签的内容,即 <1/> 形式的标签。 + * textAfter: 第四个元素表示标签之后的文本内容,即标签后紧跟着的文本。 + * eg: [undefined,undefined,1,""] + */ + const [pairedIndex, children, unpairedIndex, textAfter] = arrays.splice(0, 4); + + // 解析当前标签元素和它的子元素,返回一个包含标签索引、子元素和后续文本的数组 + const currentElement: [number, string, string] = [ + parseInt(pairedIndex || unpairedIndex), // 解析标签索引,如果是自闭合标签,则使用 unpaired + children || '', + textAfter || '', + ]; + + // 递归调用 getElements 函数,处理剩余的 arrays 数组 + const remainingElements = getElements(arrays); + + // 将当前元素和递归处理后的元素数组合并并返回 + return [currentElement, ...remainingElements]; +} + +// 对传入富文本元素的位置标志索引 +function getElementIndex(count = 0, prefix = '') { + return function () { + return `${prefix}_${count++}`; + }; +} diff --git a/packages/inula-intl/src/utils/parseRuleUtils.ts b/packages/inula-intl/src/utils/parseRuleUtils.ts index f950510b..bec392d4 100644 --- a/packages/inula-intl/src/utils/parseRuleUtils.ts +++ b/packages/inula-intl/src/utils/parseRuleUtils.ts @@ -18,6 +18,7 @@ function getType(input: any): string { return str.slice(8, -1).toLowerCase(); } +// 类型检查器 const createTypeChecker = (type: string) => { return (input: any) => { return getType(input) === type.toLowerCase(); @@ -28,24 +29,25 @@ const checkObject = (input: any) => input !== null && typeof input === 'object'; const checkRegExp = createTypeChecker('RegExp'); +// 使用正则表达式,如果对象存在则访问该属性,用来判断当前环境是否支持正则表达式sticky属性。 const checkSticky = () => typeof new RegExp('')?.sticky === 'boolean'; // 转义正则表达式中的特殊字符 -function transferReg(s: string): string { +function transferReg(str: string): string { // eslint-disable-next-line - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); } -// 计算正则表达式中捕获组的数量 -function getRegGroups(s: string): number { - const re = new RegExp('|' + s); +// 计算正则表达式中捕获组的数量,用以匹配() +function getRegGroups(str: string): number { + const regExp = new RegExp('|' + str); // eslint-disable-next-line - return re.exec('')?.length! - 1; + return regExp.exec('')?.length! - 1; } // 创建一个捕获组的正则表达式模式 -function getRegCapture(s: string): string { - return '(' + s + ')'; +function getRegCapture(str: string): string { + return '(' + str + ')'; } // 将正则表达式合并为一个联合的正则表达式模式 @@ -53,7 +55,7 @@ function getRegUnion(regexps: string[]): string { if (!regexps.length) { return '(?!)'; } - const source = regexps.map(s => '(?:' + s + ')').join('|'); + const source = regexps.map(str => '(?:' + str + ')').join('|'); return '(?:' + source + ')'; } @@ -143,7 +145,7 @@ function getRulesByArray(array: any[]) { return result; } -function getRuleOptions(type, obj) { +function getRuleOptions(type: any, obj: any) { // 如果 obj 不是一个对象,则将其转换为包含 'match' 属性的对象 if (!checkObject(obj)) { obj = { match: obj }; @@ -182,23 +184,23 @@ function getRuleOptions(type, obj) { } else { options.match = []; } - options.match.sort((a, b) => { + options.match.sort((str1: string, str2: string) => { // 根据规则的类型进行排序,确保正则表达式排在最前面,长度较长的规则排在前面 - if (checkRegExp(a) && checkRegExp(b)) { + if (checkRegExp(str1) && checkRegExp(str2)) { return 0; - } else if (checkRegExp(b)) { + } else if (checkRegExp(str2)) { return -1; - } else if (checkRegExp(a)) { + } else if (checkRegExp(str1)) { return +1; } else { - return b.length - a.length; + return str2.length - str1.length; } }); return options; } -function getRules(spec) { +function getRules(spec: any) { return Array.isArray(spec) ? getRulesByArray(spec) : getRulesByObject(spec); } diff --git a/packages/inula-intl/src/utils/utils.ts b/packages/inula-intl/src/utils/utils.ts index a3c22cb8..331dadf2 100644 --- a/packages/inula-intl/src/utils/utils.ts +++ b/packages/inula-intl/src/utils/utils.ts @@ -32,7 +32,7 @@ function compile(message: string): CompiledMessage { try { return getTokenAST(parse(message)); } catch (e) { - console.error(`Message cannot be parse due to syntax errors: ${message}`); + console.error(`Message cannot be parse due to syntax errors: ${message},cause by ${e}`); return message; } } diff --git a/packages/inula-intl/tests/core/I18n.test.ts b/packages/inula-intl/tests/core/I18n.test.ts deleted file mode 100644 index 77079664..00000000 --- a/packages/inula-intl/tests/core/I18n.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ -import I18n from '../../src/core/I18n'; - -describe('I18n', () => { - it('load catalog and merge with existing', () => { - const i18n = new I18n({}); - const messages = { - Hello: 'Hello', - }; - - i18n.loadMessage('en', messages); - i18n.changeLanguage('en'); - expect(i18n.messages).toEqual(messages); - i18n.loadMessage('fr', { Hello: 'Salut' }); - expect(i18n.messages).toEqual(messages); - }); - - it('should load multiple language ', function () { - const enMessages = { - Hello: 'Hello', - }; - const frMessage = { - Hello: 'Salut', - }; - const intl = new I18n({}); - intl.loadMessage({ - en: enMessages, - fr: frMessage, - }); - intl.changeLanguage('en'); - expect(intl.messages).toEqual(enMessages); - - intl.changeLanguage('fr'); - expect(intl.messages).toEqual(frMessage); - }); - - it('should switch active locale', () => { - const messages = { - Hello: 'Salut', - }; - - const i18n = new I18n({ - locale: 'en', - messages: { - fr: messages, - en: {}, - }, - }); - - expect(i18n.locale).toEqual('en'); - expect(i18n.messages).toEqual({}); - - i18n.changeLanguage('fr'); - expect(i18n.locale).toEqual('fr'); - expect(i18n.messages).toEqual(messages); - }); - - it('should switch active locale', () => { - const messages = { - Hello: 'Salut', - }; - - const i18n = new I18n({ - locale: 'en', - messages: { - en: messages, - fr: {}, - }, - }); - - i18n.changeLanguage('en'); - expect(i18n.locale).toEqual('en'); - expect(i18n.messages).toEqual(messages); - i18n.changeLanguage('fr'); - expect(i18n.locale).toEqual('fr'); - expect(i18n.messages).toEqual({}); - }); - it('._ allow escaping syntax characters', () => { - const messages = { - 'My \'\'name\'\' is \'{name}\'': 'Mi \'\'nombre\'\' es \'{name}\'', - }; - const i18n = new I18n({ - locale: 'es', - messages: { es: messages }, - }); - expect(i18n.formatMessage('My \'\'name\'\' is \'{name}\'')).toEqual('Mi \'nombre\' es {name}'); - }); - - it('._ should format message from catalog', function () { - const messages = { - Hello: 'Salut', - id: 'Je m\'appelle {name}', - }; - const i18n = new I18n({ - locale: 'fr', - messages: { fr: messages }, - }); - expect(i18n.locale).toEqual('fr'); - expect(i18n.formatMessage('Hello')).toEqual('Salut'); - expect(i18n.formatMessage('id', { name: 'Fred' })).toEqual('Je m\'appelle Fred'); - }); - - it('should return the formatted date and time', () => { - const i18n = new I18n({ - locale: 'fr', - }); - const formattedDateTime = i18n.formatDate('2023-06-06T07:53:54.465Z', { - dateStyle: 'full', - timeStyle: 'short', - }); - expect(typeof formattedDateTime).toBe('string'); - expect(formattedDateTime).toEqual('mardi 6 juin 2023 à 15:53'); - }); - - it('should return the formatted number', () => { - const i18n = new I18n({ - locale: 'en', - }); - const formattedNumber = i18n.formatNumber(123456.789, { style: 'currency', currency: 'USD' }); - expect(typeof formattedNumber).toBe('string'); - expect(formattedNumber).toEqual('$123,456.79'); - }); -}); diff --git a/packages/inula-intl/tests/core/I18n.test.tsx b/packages/inula-intl/tests/core/I18n.test.tsx new file mode 100644 index 00000000..cf29059c --- /dev/null +++ b/packages/inula-intl/tests/core/I18n.test.tsx @@ -0,0 +1,270 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ +import I18n from '../../src/core/I18n'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/'; + +// 测试组件 +const IndividualCustomComponent = () => { + return Custom Component; +}; + +const CustomComponent = (props: any) => { + return
{props.children}
; +}; + +const CustomComponentChildren = (props: any) => { + return
{props.children}
; +}; + +describe('I18n', () => { + it('load catalog and merge with existing', () => { + const i18n = new I18n({}); + const messages = { + Hello: 'Hello', + }; + + i18n.loadMessage('en', messages); + i18n.changeLanguage('en'); + expect(i18n.messages).toEqual(messages); + i18n.loadMessage('fr', { Hello: 'Salut' }); + expect(i18n.messages).toEqual(messages); + i18n.changeMessage({ Hello: 'Salut' }); + expect(i18n.messages).toEqual({ Hello: 'Salut' }); + }); + + it('should load multiple language ', function () { + const enMessages = { + Hello: 'Hello', + }; + const frMessage = { + Hello: 'Salut', + }; + const intl = new I18n({}); + intl.loadMessage({ + en: enMessages, + fr: frMessage, + }); + intl.changeLanguage('en'); + expect(intl.messages).toEqual(enMessages); + + intl.changeLanguage('fr'); + expect(intl.messages).toEqual(frMessage); + }); + + it('should switch active locale', () => { + const messages = { + Hello: 'Salut', + }; + + const i18n = new I18n({ + locale: 'en', + messages: { + fr: messages, + en: {}, + }, + }); + + expect(i18n.locale).toEqual('en'); + expect(i18n.messages).toEqual({}); + + i18n.changeLanguage('fr'); + expect(i18n.locale).toEqual('fr'); + expect(i18n.messages).toEqual(messages); + }); + + it('should switch active locale', () => { + const messages = { + Hello: 'Salut', + }; + + const i18n = new I18n({ + locale: 'en', + messages: { + en: messages, + fr: {}, + }, + }); + + i18n.changeLanguage('en'); + expect(i18n.locale).toEqual('en'); + expect(i18n.messages).toEqual(messages); + i18n.changeLanguage('fr'); + expect(i18n.locale).toEqual('fr'); + expect(i18n.messages).toEqual({}); + }); + it('._ allow escaping syntax characters', () => { + const messages = { + "My ''name'' is '{name}'": "Mi ''nombre'' es '{name}'", + }; + const i18n = new I18n({ + locale: 'es', + messages: { es: messages }, + }); + expect(i18n.formatMessage("My ''name'' is '{name}'")).toEqual("Mi ''nombre'' es '{name}'"); + }); + + it('._ should format message from catalog', function () { + const messages = { + Hello: 'Salut', + id: "Je m'appelle {name}", + }; + const i18n = new I18n({ + locale: 'fr', + messages: { fr: messages }, + }); + expect(i18n.locale).toEqual('fr'); + expect(i18n.formatMessage('Hello')).toEqual('Salut'); + expect(i18n.formatMessage('id', { name: 'Fred' })).toEqual("Je m'appelle Fred"); + }); + + it('should return information with html element', () => { + const messages = { + id: 'hello, {name}', + }; + const i18n = new I18n({ + locale: 'es', + messages: { es: messages }, + }); + const value = 'Jane'; + expect(i18n.formatMessage({ id: 'id' }, { name: value })).toEqual('hello, Jane'); + }); + + it('test demo from product', () => { + const messages = { + id: "服务商名称长度不能超过64个字符,允许输入中文、字母、数字、字符_-!@#$^.+'}{',且不能为关键字null(不区分大小写)。", + }; + const i18n = new I18n({ + locale: 'zh', + messages: { zh: messages }, + }); + expect(i18n.formatMessage('id')).toEqual( + "服务商名称长度不能超过64个字符,允许输入中文、字母、数字、字符_-!@#$^.+'}{',且不能为关键字null(不区分大小写)。" + ); + }); + + it('Should return information with dom element', () => { + const messages = { + richText: 'This is a rich text with a custom component: {customComponent}', + }; + const i18n = new I18n({ + locale: 'es', + messages: { es: messages }, + }); + const values = { + customComponent: , + }; + const formattedMessage = i18n.formatMessage({ id: 'richText' }, values); + + // 渲染格式化后的文本内容 + const { getByText } = render(
{formattedMessage}
); + + // 检查文本内容中是否包含自定义组件的内容 + expect(getByText('This is a rich text with a custom component')).toContain( + 'This is a rich text with a custom component' + ); + }); + + it('Should return information for nested scenes with dom elements', () => { + const messages = { + richText: 'This is a rich text with a custom component: {customComponent}', + msg: 'test', + }; + const i18n = new I18n({ + locale: 'es', + messages: { es: messages }, + }); + const values = { + customComponent: ( + + {i18n.formatMessage({ id: 'msg' })} + + ), + }; + const formattedMessage = i18n.formatMessage({ id: 'richText' }, values); + + // 渲染格式化后的文本内容 + const { getByText } = render(
{formattedMessage}
); + + // 检查文本内容中是否包含自定义组件的内容 + expect(getByText('test')).toBeTruthy(); + }); + + it('Should return information for nested scenes with dom elements', () => { + const messages = { + richText: 'This is a rich text with a custom component: {customComponent}', + msg: 'test', + }; + const i18n = new I18n({ + locale: 'es', + messages: { es: messages }, + }); + const values = { + customComponent: ( + + {i18n.formatMessage({ id: 'msg' })} + + ), + }; + const formattedMessage = i18n.formatMessage({ id: 'richText' }, values); + + // 渲染格式化后的文本内容 + const { getByText } = render(
{formattedMessage}
); + + // 检查文本内容中是否包含自定义组件的内容 + expect(getByText('test')).toBeTruthy(); + }); + + it('should be returned as value when Multiple dom elements\n', () => { + const messages = { + richText: '{today}, my name is {name}, and {age} years old!', + }; + const i18n = new I18n({ + locale: 'es', + messages: { es: messages }, + }); + const Name = () => { + return tom; + }; + const Age = () => { + return 16; + }; + const Today = () => { + return 3月2日; + }; + const values = { + today: , + name: , + age: , + }; + const formattedMessage = i18n.formatMessage({ id: 'richText' }, values); + + // 渲染格式化后的文本内容 + const { getByText } = render(
{formattedMessage}
); + + // 检查文本内容中是否包含自定义组件的内容 + expect(getByText('my name is tom, and 16 years old!')).toBeTruthy(); + }); + + it('should return the formatted date and time', () => { + const i18n = new I18n({ + locale: 'fr', + }); + const formattedDateTime = i18n.formatDate('2023-06-06T07:53:54.465Z', { + dateStyle: 'full', + timeStyle: 'short', + }); + expect(typeof formattedDateTime).toBe('string'); + expect(formattedDateTime).toEqual('mardi 6 juin 2023 à 15:53'); + }); + + it('should return the formatted number', () => { + const i18n = new I18n({ + locale: 'en', + }); + const formattedNumber = i18n.formatNumber(123456.789, { style: 'currency', currency: 'USD' }); + expect(typeof formattedNumber).toBe('string'); + expect(formattedNumber).toEqual('$123,456.79'); + }); +}); diff --git a/packages/inula-intl/tests/core/components/FormattedMessage.test.tsx b/packages/inula-intl/tests/core/components/FormattedMessage.test.tsx index 5d1bd54d..9458fc3c 100644 --- a/packages/inula-intl/tests/core/components/FormattedMessage.test.tsx +++ b/packages/inula-intl/tests/core/components/FormattedMessage.test.tsx @@ -43,7 +43,7 @@ describe('', () => { ); setTimeout(() => { - expect(getByTestId('id')).toHaveTextContent(i18n.formatMessage('hello', '', {})); + expect(getByTestId('id').textContent).toEqual(i18n.formatMessage('hello', {}, {})); }, 1000); }); it('should format context', function () { @@ -58,6 +58,6 @@ describe('', () => { ); - expect(getByTestId('id')).toHaveTextContent(i18n.formatMessage('id', { name: 'fred' }, {})); + expect(getByTestId('id').textContent).toEqual(i18n.formatMessage('id', { name: 'fred' }, {})); }); }); diff --git a/packages/inula-intl/tests/core/components/InjectI18n.test.tsx b/packages/inula-intl/tests/core/components/InjectI18n.test.tsx index de03fb00..da380e0d 100644 --- a/packages/inula-intl/tests/core/components/InjectI18n.test.tsx +++ b/packages/inula-intl/tests/core/components/InjectI18n.test.tsx @@ -42,7 +42,6 @@ describe('InjectIntl', () => { jest.spyOn(console, 'error').mockImplementation(() => {}); const Injected = injectIntl(Wrapped); - // @ts-ignore expect(() => render()).toThrow("Cannot read properties of null (reading 'i18n')"); }); @@ -53,7 +52,7 @@ describe('InjectIntl', () => { }; const { getByTestId } = mountWithProvider(); - expect(getByTestId('test')).toHaveTextContent( + expect(JSON.stringify(getByTestId('test'))).toEqual( '{"_events":{},"locale":"en","locales":["en"],"allMessages":{},"_localeData":{}}' ); }); diff --git a/packages/inula-intl/tests/core/creatI18n.test.ts b/packages/inula-intl/tests/core/creatI18n.test.ts index 2de6dbef..c7065ff3 100644 --- a/packages/inula-intl/tests/core/creatI18n.test.ts +++ b/packages/inula-intl/tests/core/creatI18n.test.ts @@ -29,6 +29,20 @@ describe('createI18n', () => { ).toBe('bar'); }); + it('createIntl', function () { + const i18n = createI18n({ + locale: 'en', + messages: { + test: 'test', + }, + }); + expect( + i18n.$t({ + id: 'test', + }) + ).toBe('test'); + }); + it('should not warn when defaultRichTextElements is not used', function () { const onWarn = jest.fn(); createI18n({ diff --git a/packages/inula-intl/tests/core/hooks/useIntl.test.tsx b/packages/inula-intl/tests/core/hooks/useIntl.test.tsx index cfe8dcd7..e69de29b 100644 --- a/packages/inula-intl/tests/core/hooks/useIntl.test.tsx +++ b/packages/inula-intl/tests/core/hooks/useIntl.test.tsx @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2023 Huawei Technologies Co.,Ltd. - * - * openInula is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -import * as React from 'react'; -import { render } from '@testing-library/react'; -import { IntlProvider, useIntl } from '../../../index'; - -const FunctionComponent = ({ spy }: { spy?: Function }) => { - const { i18n } = useIntl(); - spy!(i18n.locale); - return null; -}; - -const FC = () => { - const i18n = useIntl(); - return i18n.formatNumber(10000, { style: 'currency', currency: 'USD' }) as any; -}; - -describe('useIntl() hooks', () => { - it('throws when is missing from ancestry', () => { - // So it doesn't spam the console - jest.spyOn(console, 'error').mockImplementation(() => {}); - expect(() => render()).toThrow('I18n object is not found!'); - }); - - it('hooks onto the intl context', () => { - const spy = jest.fn(); - render( - - - - ); - expect(spy).toHaveBeenCalledWith('en'); - }); - - it('should work when switching locale on provider', () => { - const { rerender, getByTestId } = render( - - - - - - ); - expect(getByTestId('comp')).toMatchSnapshot(); - rerender( - - - - - - ); - expect(getByTestId('comp')).toMatchSnapshot(); - rerender( - - - - - - ); - - expect(getByTestId('comp')).toMatchSnapshot(); - }); -}); diff --git a/packages/inula-intl/tests/format/cache/cache.test.ts b/packages/inula-intl/tests/format/cache/cache.test.ts index e9401710..6944b498 100644 --- a/packages/inula-intl/tests/format/cache/cache.test.ts +++ b/packages/inula-intl/tests/format/cache/cache.test.ts @@ -15,7 +15,7 @@ import creatI18nCache from '../../../src/format/cache/cache'; describe('creatI18nCache', () => { - it('should create an empty IntlCache object', () => { + it('should create an empty I18nCache object', () => { const intlCache = creatI18nCache(); expect(intlCache).toEqual({ diff --git a/packages/inula-intl/tests/format/formatters/DateTimeFormatter.test.ts b/packages/inula-intl/tests/format/formatters/DateTimeFormatter.test.ts index 7396c273..46d2933b 100644 --- a/packages/inula-intl/tests/format/formatters/DateTimeFormatter.test.ts +++ b/packages/inula-intl/tests/format/formatters/DateTimeFormatter.test.ts @@ -61,7 +61,7 @@ describe('DateTimeFormatter', () => { expect(spy).toHaveBeenCalledWith('en-GB', { month: 'short' }); }); - it('should not memoize formatter instances when memoize is false', () => { + it('should not memoize formatter instances when cache is effective', () => { const spy = jest.spyOn(Intl, 'DateTimeFormat'); const formatter1 = new DateTimeFormatter('en-US', { month: 'short' }); const formatter2 = new DateTimeFormatter('en-US', { month: 'short' }); @@ -91,7 +91,7 @@ describe('DateTimeFormatter', () => { expect(formatted).toEqual('January 1, 2023'); }); - it('should format using memorized formatter when useMemorize is true', () => { + it('should format using memorized formatter when cache is effective', () => { const formatter = new DateTimeFormatter('en-US', { year: 'numeric' }, creatI18nCache()); const date = new Date(2023, 0, 1); const formatted1 = formatter.dateTimeFormat(date); diff --git a/packages/inula-intl/tests/format/getFormatMessage.test.ts b/packages/inula-intl/tests/format/getFormatMessage.test.ts index f64c842f..a1ec17f8 100644 --- a/packages/inula-intl/tests/format/getFormatMessage.test.ts +++ b/packages/inula-intl/tests/format/getFormatMessage.test.ts @@ -24,7 +24,7 @@ describe('getFormatMessage', () => { }, }, locale: 'en', - error: 'missingMessage', + onError: 'missingMessage', }); it('should return the correct translation for an existing message ID', () => { @@ -32,7 +32,7 @@ describe('getFormatMessage', () => { const values = { name: 'John' }; const expectedResult = 'Hello, John!'; - const result = getFormatMessage(i18nInstance, id, values); + const result = getFormatMessage(i18nInstance, id, values, {}, {}); expect(result).toEqual(expectedResult); }); @@ -41,7 +41,7 @@ describe('getFormatMessage', () => { const id = 'missingMessage'; const expectedResult = 'missingMessage'; - const result = getFormatMessage(i18nInstance, id); + const result = getFormatMessage(i18nInstance, id, {}, {}, {}); expect(result).toEqual(expectedResult); }); diff --git a/packages/inula-intl/tests/utils/copyStatics.test.ts b/packages/inula-intl/tests/utils/copyStatics.test.ts index ead6a6a8..d3d76228 100644 --- a/packages/inula-intl/tests/utils/copyStatics.test.ts +++ b/packages/inula-intl/tests/utils/copyStatics.test.ts @@ -15,7 +15,7 @@ import copyStaticProps from '../../src/utils/copyStaticProps'; describe('copyStaticProps', () => { - test('should hoist static properties from sourceComponent to targetComponent', () => { + it('should hoist static properties from sourceComponent to targetComponent', () => { class SourceComponent { static staticProp = 'sourceProp'; } @@ -23,11 +23,10 @@ describe('copyStaticProps', () => { class TargetComponent {} copyStaticProps(TargetComponent, SourceComponent); - expect((TargetComponent as any).staticProp).toBe('sourceProp'); }); - test('should hoist static properties from inherited components', () => { + it('should hoist static properties from inherited components', () => { class SourceComponent { static staticProp = 'sourceProp'; } @@ -37,11 +36,10 @@ describe('copyStaticProps', () => { class TargetComponent {} copyStaticProps(TargetComponent, InheritedComponent); - expect((TargetComponent as any).staticProp).toBe('sourceProp'); }); - test('should not hoist properties if descriptor is not valid', () => { + it('should not hoist properties if descriptor is not valid', () => { class SourceComponent { get staticProp() { return 'sourceProp'; @@ -51,11 +49,10 @@ describe('copyStaticProps', () => { class TargetComponent {} copyStaticProps(TargetComponent, SourceComponent); - expect((TargetComponent as any).staticProp).toBeUndefined(); }); - test('should not hoist properties if descriptor is not valid', () => { + it('should not hoist properties if descriptor is not valid', () => { class SourceComponent { static get staticProp() { return 'sourceProp'; @@ -65,11 +62,10 @@ describe('copyStaticProps', () => { class TargetComponent {} copyStaticProps(TargetComponent, SourceComponent); - expect((TargetComponent as any).staticProp).toBe('sourceProp'); }); - test('copyStaticProps should not copy static properties that already exist in target or source component', () => { + it('copyStaticProps should not copy static properties that already exist in target or source component', () => { const targetComponent = { staticProp: 'target' }; const sourceComponent = { staticProp: 'source' }; copyStaticProps(targetComponent, sourceComponent); diff --git a/packages/inula-intl/tests/utils/eventListener.test.ts b/packages/inula-intl/tests/utils/eventListener.test.ts index 17e0f43b..c72af6cf 100644 --- a/packages/inula-intl/tests/utils/eventListener.test.ts +++ b/packages/inula-intl/tests/utils/eventListener.test.ts @@ -43,7 +43,7 @@ describe('eventEmitter', () => { expect(listener).not.toBeCalled(); }); - it('should do nothing when even doesn\'t exist', () => { + it("should do nothing when even doesn't exist", () => { const unknown = jest.fn(); const emitter = new EventEmitter(); diff --git a/packages/inula-intl/tsconfig.json b/packages/inula-intl/tsconfig.json index f3ff266e..7bc1bfc6 100644 --- a/packages/inula-intl/tsconfig.json +++ b/packages/inula-intl/tsconfig.json @@ -31,6 +31,7 @@ "declaration": true, "experimentalDecorators": true, "downlevelIteration": true, + "emitDeclarationOnly": true, "declarationDir": "./build/@types", // 赋值为空数组使@types/node不会起作用 "lib": [ @@ -54,7 +55,8 @@ } }, "include": [ - "./index.ts" + "./index.ts", + ], "exclude": [ "node_modules", diff --git a/packages/inula-intl/webpack.config.js b/packages/inula-intl/webpack.config.js index 36c05a33..dc0ddb3b 100644 --- a/packages/inula-intl/webpack.config.js +++ b/packages/inula-intl/webpack.config.js @@ -12,16 +12,18 @@ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. */ - -const { resolve } = require('path'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); +import path from 'path'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import { fileURLToPath } from 'url'; const isDevelopment = process.env.NODE_ENV === 'development'; const entryPath = './example/index.tsx'; -module.exports = { - entry: resolve(__dirname, entryPath), +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +export default { + entry: path.join(__dirname, entryPath), output: { - path: resolve(__dirname, './build'), + path: path.join(__dirname, './build'), filename: 'main.js', }, module: { @@ -50,7 +52,7 @@ module.exports = { mode: isDevelopment ? 'development' : 'production', plugins: [ new HtmlWebpackPlugin({ - template: resolve(__dirname, './example/index.html'), + template: path.join(__dirname, './example/index.html'), }), ], resolve: { diff --git a/packages/inula-request/babel.config.js b/packages/inula-request/babel.config.js index 383563ed..f373490c 100644 --- a/packages/inula-request/babel.config.js +++ b/packages/inula-request/babel.config.js @@ -14,11 +14,5 @@ */ module.exports = { - presets: [ - [ - '@babel/preset-env', - { targets: { node: 'current' }}, - ], - ['@babel/preset-typescript'], - ], + presets: [['@babel/preset-env', { targets: { node: 'current' } }], ['@babel/preset-typescript']], }; diff --git a/packages/inula-request/examples/cancelRequest/cancelRequestTest.html b/packages/inula-request/examples/cancelRequest/cancelRequestTest.html index f9a4ebcd..b5367215 100644 --- a/packages/inula-request/examples/cancelRequest/cancelRequestTest.html +++ b/packages/inula-request/examples/cancelRequest/cancelRequestTest.html @@ -16,26 +16,24 @@