fix react plugin
This commit is contained in:
parent
5904297e35
commit
d9af3b9321
@ -18,7 +18,15 @@
|
||||
```
|
||||
|
||||
## 第二步:导入`t`翻译函数
|
||||
无论采用何种工具创建`React`应用,均可以直接从`languages`直接导入`t`函数。
|
||||
|
||||
`t`翻译函数用来进行文件翻译,普通的`React`应用`t`翻译函数可以用在两个地方:
|
||||
|
||||
- 普通的`js`或`ts`文件
|
||||
- `React`组件`jsx、tsx`文件
|
||||
|
||||
### 在`js`或`ts`文件中使用
|
||||
|
||||
只需要从`languages`直接导入`t`函数即可。
|
||||
|
||||
```javascript | pure
|
||||
import { t } from "./languages"
|
||||
@ -33,42 +41,116 @@ import { t } from "../../../languages"
|
||||
|
||||
导入`t`函数后就可以直接使用了。
|
||||
|
||||
## 第三步:自动导入`t`翻译函数
|
||||
### 在`React`组件中翻译
|
||||
|
||||
当源码文件非常多时,手动导入`t`函数比较麻烦,我们提供了`vite`和`babel`两个插件可以实现自动导入`t`函数。
|
||||
如果应用是采用`Vite`+`@vitejs/plugin-react`创建的工程,则可以通过配置`@voerkai18n/vite`插件实现自动导入`t`函数。
|
||||
在`React`组件中使用`t`函数翻译与在`js`或`ts`文件中使用的最大区别在于:**当切换语言时,需要触发组件的重新渲染**。
|
||||
|
||||
- **配置根组件Provider**
|
||||
|
||||
使用`VoerkaI18nProvider`包装应用根组件,本质上是创建了一个`VoerkaI18nContext.Provider`
|
||||
|
||||
```jsx | pure
|
||||
|
||||
// 1.当前语言Scope
|
||||
import { i18nScope } from "./languages"
|
||||
import { VoerkaI18nProvider } from "@voerkai18n/react"
|
||||
|
||||
export default App(){
|
||||
return (
|
||||
<VoerkaI18nProvider scope={i18nScope}>
|
||||
<MyComponent/>
|
||||
<VoerkaI18nProvider/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- **组件中使用翻译函数**
|
||||
|
||||
通过`useVoerkaI18n`获取当前作用域的`t`翻译函数。
|
||||
|
||||
```jsx | pure
|
||||
import { useVoerkaI18n } from "@voerkai18n/react"
|
||||
export function MyComponent(){
|
||||
const { t } = useVoerkaI18n()
|
||||
return (
|
||||
<div>{t("要翻译的内容")}</div>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
注意:在组件中直接使用`import { t } from "languages`也是可以工作的,因为本质上`t`函数仅仅是一个普通的函数。但是当动态切换语言时,对应的组件不能自动重新渲染。因此,需要使用`{ t } = useVoerkaI18n()`导入`t`函数,才可以在切换语言时自动重新渲染组件。
|
||||
|
||||
### 第三步: 自动导入`t`翻译函数
|
||||
|
||||
如果应用是采用`Vite`+`@vitejs/plugin-react`创建的工程,则可以通过配置`@voerkai18n/vite`插件实现自动导入`t`函数和`翻译内容自动映射`等。
|
||||
|
||||
在`vite.config.js`中配置导入安装`@voerkai18n/vite`插件。
|
||||
|
||||
```typescript | pure
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import Inspect from 'vite-plugin-inspect'
|
||||
import Voerkai18nPlugin from "@voerkai18n/vite"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
Inspect(), // localhost:3000/__inspect/
|
||||
Voerkai18nPlugin({ debug: true }),
|
||||
react()
|
||||
]
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
详见`@voerkai18n/vite`插件介绍。
|
||||
|
||||
`@voerkai18n/vite`插件主要干两件事情:
|
||||
|
||||
- 对`js/ts`文件自动导入`t`函数,`jsx/tsx`文件不需要自动导入,只能使用`useVoerkaI18n`。
|
||||
- 根据`idMap.(js|ts)`内容自动替换翻译内容,用来消除冗余内容。
|
||||
|
||||
|
||||
## 第四步:切换语言
|
||||
|
||||
最后,一般需要在应用中提供切换语言并自动重新渲染界面的功能。针对`React`应用,提供了`useVoerkaI18n`来实现此功能。
|
||||
接下来在一般我们还需要实现语言切换的功能界面,`useVoerkaI18n`提供了:
|
||||
- `language`: 当前激活语言名称
|
||||
- `defaultLanguage`: 默认语言名称
|
||||
- `changeLanguage(language)`: 用来切换当前语言
|
||||
- `languages`: 读取当应用支持的语言列表。
|
||||
|
||||
|
||||
```jsx | pure
|
||||
// 如果没有在vite.config.js中配置`@voerkai18n/vite`插件,则需要手工导入t函数
|
||||
import { t } from "./languages"
|
||||
|
||||
import { useVoerkaI18n } from "@voerkai18n/react"
|
||||
export default App(){
|
||||
const { activeLanguage,changeLanguage,languages } = useVoerkaI18n()
|
||||
return (<div>
|
||||
<h1>{t("当前语言")}:{activeLanguage}</h1>
|
||||
<div> {
|
||||
languages.map(lang=>{
|
||||
return (<button
|
||||
key={lang.name}
|
||||
onclick={()=>changeLanguage(lang.name)}>
|
||||
{lang.title}
|
||||
</button>)
|
||||
})}
|
||||
</div>
|
||||
</div> )
|
||||
|
||||
export function MyComponent(){
|
||||
const { t, language,changeLanguage,languages,defaultLanguage } = useVoerkaI18n()
|
||||
return (
|
||||
<div>
|
||||
<h1>{t("当前语言")}:{language}</h1>
|
||||
<h1>{t("默认语言")}:{defaultLanguage}</h1>
|
||||
<div> {
|
||||
languages.map(lang=>{
|
||||
return (<button
|
||||
key={lang.name}
|
||||
onclick={()=>changeLanguage(lang.name)}>
|
||||
{lang.title}
|
||||
</button>)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 小结
|
||||
|
||||
- `useVoerkaI18n`返回当前激活语言、切换语言函数、支持的语言列表。
|
||||
- 如果需要在切换语言时进行全局重新渲染,一般需要在顶层`App组件`中使用此`hook`, 这样可以确保在切换语言时整个应用进行重新渲染。
|
||||
- 一般切换语言的功能界面不会直接在`App组件`中使用,您可以使用一个专门的组件来切换语言。
|
||||
- 使用`<VoerkaI18nProvider scope={i18nScope}>`封装根组件
|
||||
- `const { t } = useVoerkaI18n()`来导入翻译函数
|
||||
- 使用`const { language,changeLanguage } = useVoerkaI18n()`来访问切换语言的函数
|
||||
- 在普通`ts/js`文件中使用`import { t } from "./languages"`来导入`t`翻译函数
|
||||
- `@voerkai18n/vite`插件是可选的,仅仅普通`ts/js`文件使用`t`翻译函数时用来自动导入。
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
|
||||
|
||||
console.log(t("这是一个测试"))
|
@ -7,7 +7,7 @@ import Voerkai18nPlugin from "@voerkai18n/vite"
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
Inspect(), // localhost:3000/__inspect/
|
||||
// Voerkai18nPlugin({ debug: true }),
|
||||
Voerkai18nPlugin({ debug: true }),
|
||||
react()
|
||||
]
|
||||
})
|
||||
|
@ -1,458 +0,0 @@
|
||||
/**
|
||||
* 用于多包环境下的自动发布
|
||||
*
|
||||
* autopublish
|
||||
*
|
||||
* 1.在package.json中添加scripts
|
||||
* {
|
||||
* scripts:{
|
||||
* "publish":"autopublish [options]",
|
||||
* }
|
||||
* }
|
||||
* 2. 参数
|
||||
* -q: 默认情况下会比对最后一次发布的时间,来决定是否自动发布
|
||||
* 当-q参数被指定时,会询问用户
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require("fs-extra");
|
||||
const inquirer = require("inquirer");
|
||||
const semver = require("semver")
|
||||
const path = require("path");
|
||||
const shelljs = require("shelljs");
|
||||
const createLogger = require("logsets");
|
||||
const TaskListPlugin = require("logsets/plugins/tasklist")
|
||||
const TablePlugin = require("logsets/plugins/table")
|
||||
|
||||
const { Command ,Option} = require('commander');
|
||||
|
||||
const dayjs = require("dayjs");
|
||||
const relativeTime = require("dayjs/plugin/relativeTime");
|
||||
const { rejects } = require("assert");
|
||||
const { Console } = require("console");
|
||||
const { resourceLimits } = require("worker_threads");
|
||||
dayjs.extend(relativeTime);
|
||||
require('dayjs/locale/zh-CN')
|
||||
dayjs.locale("zh-CN");
|
||||
|
||||
const logger = createLogger();
|
||||
logger.use(TaskListPlugin)
|
||||
logger.use(TablePlugin)
|
||||
|
||||
const program =new Command()
|
||||
|
||||
// 排除要发布的包
|
||||
const exclude_packages = ["autopublish"]
|
||||
|
||||
function getPackages(){
|
||||
let workspaceRoot = process.cwd()
|
||||
if(!fs.existsSync(path.join(workspaceRoot,"pnpm-workspace.yaml"))){
|
||||
console.log("命令只能在工作区根目录下执行")
|
||||
return
|
||||
}
|
||||
// 读取所有包
|
||||
let packages = fs.readdirSync(path.join(workspaceRoot,"packages")).map(packageName=>{
|
||||
const packageFolder = path.join(workspaceRoot,"packages",packageName)
|
||||
const pkgFile = path.join(workspaceRoot,"packages",packageName,"package.json")
|
||||
if(fs.existsSync(pkgFile)){
|
||||
const { name, version,lastPublish,dependencies,devDependencies,description }= fs.readJSONSync(pkgFile)
|
||||
// 读取工作区包依赖
|
||||
let packageDependencies =[]
|
||||
Object.entries({...dependencies,...devDependencies}).forEach(([name,version])=>{
|
||||
if(version.startsWith("workspace:") && !exclude_packages.includes(name.replace("@voerkai18n/",""))){
|
||||
packageDependencies.push(name)
|
||||
}
|
||||
})
|
||||
return {
|
||||
name, // 完整包名
|
||||
description,
|
||||
value:packageName, // 文件夹名称
|
||||
version,
|
||||
lastPublish,
|
||||
isDirty: packageIsDirty(packageFolder), // 包自上次发布之后是否已修改
|
||||
dependencies:packageDependencies // 依赖的工作区包
|
||||
}
|
||||
}
|
||||
}).filter(pkgInfo=>pkgInfo && !exclude_packages.includes(pkgInfo.value))
|
||||
|
||||
// 根据依赖关系进行排序
|
||||
for(let i=0;i<packages.length;i++){
|
||||
for(let j=i;j<packages.length;j++){
|
||||
let pkgInfo2 = packages[j]
|
||||
if( packages[i].dependencies.includes(pkgInfo2.name)){
|
||||
let p = packages[i]
|
||||
packages[i] = packages[j]
|
||||
packages[j] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果某个包isDirty=true,则依赖于其的其他包isDirty=true
|
||||
packages.forEach(package => {
|
||||
if(package.isDirty){
|
||||
packages.forEach(p=>{
|
||||
if(p.name!==package.name && p.dependencies.includes(package.name)){
|
||||
p.isDirty = true
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return packages
|
||||
}
|
||||
|
||||
function assertInWorkspaceRoot(){
|
||||
const workspaceRoot = process.cwd()
|
||||
if(!fs.existsSync(path.join(workspaceRoot,"pnpm-workspace.yaml"))){
|
||||
throw new Error("命令只能在工作区根目录下执行")
|
||||
}
|
||||
}
|
||||
|
||||
function assertInPackageRoot(){
|
||||
const currentFolder = process.cwd()
|
||||
const workspaceRoot = path.join(currentFolder,"../../")
|
||||
|
||||
const inPackageRoot = fs.existsSync(path.join(currentFolder,"package.json")) && fs.existsSync(path.join(workspaceRoot,"pnpm-workspace.yaml"))
|
||||
|
||||
if(!inPackageRoot){
|
||||
throw new Error("命令只能在工作区的包目录下执行")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行脚本,出错会返回错误信息
|
||||
* @param {*} script
|
||||
*/
|
||||
function execShellScript(script,options={}){
|
||||
let {code,stdout} = shelljs.exec(script,options)
|
||||
if(code>0){
|
||||
new Error(`执行<${script}>失败: ${stdout.trim()}`)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 异步执行脚本
|
||||
* @param {*} script
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
async function asyncExecShellScript(script,options={}){
|
||||
return new Promise((resolve,reject)=>{
|
||||
shelljs.exec(script,{...options,async:true},(code,stdout)=>{
|
||||
if(code>0){
|
||||
reject(new Error(`执行<${script}>失败: ${stdout.trim()}`))
|
||||
}else{
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 执行脚本并返回结果
|
||||
* @param {*} script
|
||||
*/
|
||||
function execShellScriptReturns(script,options={}){
|
||||
return shelljs.exec(script,options).stdout.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指定包最近一次更新的时间
|
||||
* 通过遍历所有文件夹
|
||||
* @param {*} folder
|
||||
* @returns
|
||||
*/
|
||||
function getFolderLastModified(folder,patterns=[],options={}){
|
||||
patterns.push(...[
|
||||
"package.json",
|
||||
"**",
|
||||
"**/*",
|
||||
"!node_modules/**",
|
||||
"!node_modules/**/*",
|
||||
"!**/node_modules/**",
|
||||
"!**/node_modules/**/*",
|
||||
])
|
||||
|
||||
const glob = require("fast-glob")
|
||||
let files = glob.sync(patterns, {
|
||||
cwd: folder,
|
||||
absolute:true,
|
||||
...options
|
||||
})
|
||||
let lastUpdateTime = null
|
||||
for(let file of files){
|
||||
const { mtimeMs } = fs.statSync(file)
|
||||
lastUpdateTime = lastUpdateTime ? Math.max(lastUpdateTime,mtimeMs) : mtimeMs
|
||||
}
|
||||
return lastUpdateTime
|
||||
}
|
||||
|
||||
function getFileLastModified(file){
|
||||
const { mtimeMs } = fs.statSync(file)
|
||||
return mtimeMs
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} packageInfo {name:"@voerkai18n/autopublish",value:"",version:"1.0.0",lastPublish:"2020-05-01T00:00:00.000Z"}
|
||||
*/
|
||||
async function runPackageScript(workspaceRoot,packageInfo,{silent=false}={}){
|
||||
const packageFolder = path.join(workspaceRoot,"packages",packageInfo.value)
|
||||
const package = fs.readJSONSync(path.join(packageFolder,"package.json"))
|
||||
const lastModified = getFolderLastModified(packageFolder)
|
||||
// 进入包所在的文件夹
|
||||
shelljs.cd(packageFolder)
|
||||
// 每个包必须定义自己的发布脚本
|
||||
if("release" in package.scripts){
|
||||
await asyncExecShellScript(`pnpm release`,{silent})
|
||||
}else{
|
||||
const reason = `包[{}]没有定义自动发布脚本release`
|
||||
if(showLog) logger.log(reason,package.name)
|
||||
throw new Error(`未配置<release>脚本`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 返回指定包自上次发布之后是否有更新过
|
||||
*
|
||||
* @param {*} packageFolder
|
||||
*
|
||||
*
|
||||
*/
|
||||
function packageIsDirty(packageFolder){
|
||||
const pkgFile = path.join(packageFolder,"package.json")
|
||||
if(!fs.existsSync(pkgFile)){
|
||||
logger.log("当前包[{}]不存在package.json文件",packageFolder)
|
||||
throw new Error("当前包不存在package.json文件")
|
||||
}
|
||||
const package = fs.readJSONSync(pkgFile)
|
||||
const lastModified = getFolderLastModified(packageFolder)
|
||||
const lastPublish = package.lastPublish
|
||||
// 由于上一次发布时会更新package.json文件,如果最后更新的文件时间==package.json文件最后更新时间,则说明没有更新
|
||||
const pkgLastModified = getFileLastModified(pkgFile)
|
||||
|
||||
return dayjs(lastModified).isAfter(dayjs(lastPublish)) && !dayjs(pkgLastModified).isSame(dayjs(lastModified))
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布所有包
|
||||
*
|
||||
* 将比对最后发布时间和最后修改时间的差别来决定是否发布
|
||||
*
|
||||
*
|
||||
* @param {*} packages
|
||||
*/
|
||||
async function publishAllPackages(packages,options={}){
|
||||
const tasks = logger.tasklist()
|
||||
const workspaceRoot = process.cwd()
|
||||
// 依次对每个包进行发布
|
||||
for(let package of packages){
|
||||
tasks.add(`发布包[${package.name}]`)
|
||||
try{
|
||||
if(package.isDirty){
|
||||
await runPackageScript(workspaceRoot,package,{silent:true,...options})
|
||||
let { version } = fs.readJSONSync(path.join(workspaceRoot,"packages",package.value,"package.json"))
|
||||
tasks.complete(`${package.version}->${version}`)
|
||||
}else{
|
||||
tasks.skip()
|
||||
}
|
||||
}catch(e){
|
||||
tasks.error(`${e.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 发布包,并且在package.json中记录最后发布时间
|
||||
* 本命令只能在包文件夹下执行
|
||||
* @param {*} options
|
||||
*/
|
||||
async function publishPackage(options){
|
||||
const { versionIncrementStep,silent=true } = options
|
||||
|
||||
// 此命令需要切换到包所在目录
|
||||
const packageFolder = process.cwd()
|
||||
const packageName = path.basename(packageFolder)
|
||||
const pkgFile = path.join(packageFolder,"package.json")
|
||||
|
||||
if(!fs.existsSync(pkgFile)){
|
||||
logger.log("当前包[{}]不存在package.json文件",packageName)
|
||||
throw new Error("当前包不存在package.json文件,请在包文件夹下执行")
|
||||
}
|
||||
|
||||
let package = fs.readJSONSync(pkgFile)
|
||||
const oldVersion = package.version
|
||||
let packageBackup = Object.assign({},package) // 备份package.json,当操作失败时,还原
|
||||
|
||||
logger.log("发布包:{}",`@voerkai18n/${packageName}`)
|
||||
|
||||
const tasks = logger.tasklist()
|
||||
|
||||
try{
|
||||
// 第一步: 更新版本号和发布时间
|
||||
tasks.add("更新版本号")
|
||||
await asyncExecShellScript(`npm version ${versionIncrementStep}`,{silent})
|
||||
// 重新读取包
|
||||
package = fs.readJSONSync(pkgFile)
|
||||
packageBackup = Object.assign({},package)
|
||||
tasks.complete(`${oldVersion}->${package.version}`)
|
||||
|
||||
// 第二步:构建包
|
||||
if("build" in package.scripts){
|
||||
tasks.add("构建包")
|
||||
await asyncExecShellScript(`pnpm build`,{silent})
|
||||
tasks.complete()
|
||||
}
|
||||
|
||||
|
||||
// 第三步:发布
|
||||
// 由于工程可能引用了工作区内的其他包,必须pnpm publish才能发布
|
||||
// pnpm publish会修正引用工作区其他包到的依赖信息,而npm publish不能识别工作区内的依赖,会导致报错
|
||||
tasks.add("发布包")
|
||||
await asyncExecShellScript(`pnpm publish --no-git-checks --access public`,{silent})
|
||||
tasks.complete()
|
||||
|
||||
// 第四步:更新发布时间
|
||||
tasks.add("更新发布时间")
|
||||
package.lastPublish = dayjs().format()
|
||||
fs.writeFileSync(pkgFile,JSON.stringify(package,null,4))
|
||||
tasks.complete()
|
||||
|
||||
}catch(e){// 如果发布失败,则还原package.json
|
||||
fs.writeFileSync(pkgFile,JSON.stringify(packageBackup,null,4))
|
||||
tasks.error(`${e.message}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
program
|
||||
.command("list")
|
||||
.description("列出各个包的最后一次提交时间和版本信息")
|
||||
.action(options => {
|
||||
assertInWorkspaceRoot()
|
||||
workspaceRoot = process.cwd()
|
||||
const table = logger.table({grid:1})
|
||||
table.addHeader("包名","版本号","最后提交时间","最后修改时间")
|
||||
getPackages().forEach(package => {
|
||||
const lastPublish = package.lastPublish ? dayjs(package.lastPublish).format("MM/DD hh:mm:ss") : "None"
|
||||
const lastPublishRef = package.lastPublish ? `(${dayjs(package.lastPublish).fromNow()})` : ""
|
||||
const lastModified = getFolderLastModified(path.join(workspaceRoot,"packages",package.value))
|
||||
const lastUpdate = dayjs(lastModified).format("MM/DD hh:mm:ss")
|
||||
const lastUpdateRef = dayjs(lastModified).fromNow()
|
||||
if(package.lastPublish){
|
||||
table.addRow(package.name,package.version,`${lastPublish}(${lastPublishRef})`,`${lastUpdate}(${lastUpdateRef})`)
|
||||
}else{
|
||||
table.addRow(package.name,package.version,"None",`${lastUpdate}(${lastUpdateRef})`)
|
||||
}
|
||||
})
|
||||
table.render()
|
||||
generatePackageVersionDoc()
|
||||
})
|
||||
|
||||
// 生成包版本列表文件到文档中
|
||||
function generatePackageVersionDoc(){
|
||||
let workspaceRoot = path.join(__dirname,"../../")
|
||||
shelljs.cd(workspaceRoot)
|
||||
let results = []
|
||||
results.push("# 版本信息")
|
||||
results.push("| 包| 版本号| 最后更新|说明|")
|
||||
results.push("| --- | :---:| --- |---|")
|
||||
getPackages().forEach(package => {
|
||||
const lastPublish = package.lastPublish ? dayjs(package.lastPublish).format("YYYY/MM/DD") : "None"
|
||||
results.push(`|**${package.name}**|${package.version}|${lastPublish}|${package.description}|`)
|
||||
})
|
||||
|
||||
fs.writeFileSync(path.join(workspaceRoot,"docs/src/guide/intro/versions.md"), results.join("\n"))
|
||||
}
|
||||
|
||||
async function answerForSelectPackages(packages,options){
|
||||
const workspaceRoot = process.cwd()
|
||||
return new Promise((resolve,reject) => {
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: "checkbox",
|
||||
name: "selectPackages",
|
||||
message: "请选择要发布的库:",
|
||||
choices: packages.map(package => {
|
||||
const lastPublish = package.lastPublish ? dayjs(package.lastPublish).format("MM/DD hh:mm:ss") : "None"
|
||||
const lastPublishRef = package.lastPublish ? `(${dayjs(package.lastPublish).fromNow()})` : ""
|
||||
const lastModified = getFolderLastModified(path.join(workspaceRoot,"packages",package.value))
|
||||
const lastUpdate = dayjs(lastModified).format("MM/DD hh:mm:ss")
|
||||
const lastUpdateRef = dayjs(lastModified).fromNow()
|
||||
return {
|
||||
...package,
|
||||
value: package,
|
||||
name:`${package.name.padEnd(24)}Version: ${package.version.padEnd(8)} LastPublish: ${lastPublish.padEnd(16)}${lastPublishRef} lastModified: ${lastUpdate}(${lastUpdateRef})`, }
|
||||
}),
|
||||
pageSize:12,
|
||||
loop: false
|
||||
},
|
||||
])
|
||||
.then((answers) => {
|
||||
resolve(answers.selectPackages)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.log(error.message)
|
||||
reject(error)
|
||||
});
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 发布包的模式
|
||||
*
|
||||
* 1. 在包中使用
|
||||
* {
|
||||
* scripts:{
|
||||
* "release":"pnpm autopublish"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 2. 发布所有包
|
||||
* {
|
||||
* scripts:{
|
||||
* "autopublish":"pnpm autopublish -a"
|
||||
* }
|
||||
* }
|
||||
* > pnpm autopublish -- -a // 自动发布,会询问要发布的
|
||||
* > pnpm autopublish -- -a --no-ask // 自动发布,不会询问全自动发布
|
||||
*
|
||||
*/
|
||||
const VERSION_STEPS = ["major", "minor", "patch","premajor","preminor","prepatch","prerelease"]
|
||||
program
|
||||
.description("自动发布包")
|
||||
.option("-a, --all", "发布所有包")
|
||||
.option("-n, --no-ask", "不询问")
|
||||
.option("-s, --no-silent", "静默显示脚本输出")
|
||||
.addOption(new Option('-i, --version-increment-step [value]', '版本增长方式').default("patch").choices(VERSION_STEPS))
|
||||
.action(async (options) => {
|
||||
// 发布所有包时只能在工作区根目录下执行
|
||||
if(options.all){
|
||||
assertInWorkspaceRoot()
|
||||
}else{// 发布指定包时只能在包目录下执行
|
||||
assertInPackageRoot()
|
||||
}
|
||||
if(options.all){ // 自动发布所有包
|
||||
const workspaceRoot = process.cwd()
|
||||
let packages = getPackages()
|
||||
if(options.ask){
|
||||
packages = await answerForSelectPackages(packages,options)
|
||||
}
|
||||
if(packages.length > 0){
|
||||
await publishAllPackages(packages,options)
|
||||
}
|
||||
}else{// 只发布指定的包
|
||||
await publishPackage(options)
|
||||
}
|
||||
// 在文档中输出各包的版本信息
|
||||
generatePackageVersionDoc()
|
||||
})
|
||||
|
||||
program.parseAsync(process.argv);
|
||||
|
||||
|
@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "@voerkai18n/autopublish",
|
||||
"version": "1.0.3",
|
||||
"description": "自动发布工作区的包",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"release": "node ./index.js publish"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"autopublish": "./index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^9.0.0",
|
||||
"fast-glob": "^3.2.11"
|
||||
},
|
||||
"lastPublish": "2022-04-06T15:36:15+08:00"
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
|
||||
|
||||
# 概述
|
||||
|
||||
`@voerkai18n`项目是一个标准的`monorepo`包工程,包含了`@voerkai18n/cli`、`@voerkai18n/runtime`、`@voerkai18n/utils`、`@voerkai18n/vue`、`@voerkai18n/vite`、`@voerkai18n/babel`、`@voerkai18n/react`、`@voerkai18n/formatters`等多个包,发布包时容易引起混乱问题,最大问题时:
|
||||
- 经常忘记哪个包最近什么时间修改,哪个包应该发布。
|
||||
- 由于包之间存在依赖关系,需要按一定的顺序进行发布
|
||||
|
||||
`@voerkai18n/autopublish`用来实现全自动或手动辅助进行发布。
|
||||
|
||||
**源码与文档:**[https://gitee.com/zhangfisher/voerka-i18n](https://gitee.com/zhangfisher/voerka-i18n)
|
||||
|
||||
[](https://gitee.com/zhangfisher/voerka-i18n)
|
||||
|
||||
# 使用
|
||||
|
||||
## 准备
|
||||
|
||||
`@voerkai18n/autopublish`用于采用`pnpmp`创建的`monorepo`包工程,不支持`lerna/yarn`。
|
||||
按照常规约定,包存放在`<projectRoot>/packages/<name>`。
|
||||
|
||||
## 第一步:配置包的发布脚本
|
||||
|
||||
将`@voerkai18n/autopublish`添加为包的开发依赖。
|
||||
|
||||
```javascript
|
||||
// 进入包文件夹后执行
|
||||
> pnpm add -D @voerkai18n/autopublish
|
||||
```
|
||||
然后,配置发布脚本:
|
||||
```json
|
||||
{
|
||||
"scripts":{
|
||||
"build":"默认的包构建命令",
|
||||
"release":"pnpm autopublish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@voerkai18n/autopublish": "workspace:^1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 发布脚本必须为`release`,不能是其他名称,特别是`publish`
|
||||
- `pnpm autopublish`也可以在包路径下单独执行。
|
||||
- `pnpm autopublish`默认依次执行:
|
||||
- `npm version patch`:升级版本号
|
||||
- `pnpm run build`(可选)
|
||||
- `pnpm publish --no-git-checks --access publish`
|
||||
- 默认每次发布均会升级`patch`版本号,可以通过`pnpm autopublish -i <版本递增方式>`来增加版本号,递增方式可选:[`"major"`, `"minor"`, `"patch"`,`"premajor"`,`"preminor"`,`"prepatch"`,`"prerelease"`]
|
||||
- 每次执行`pnpm autopublish`均会在当前包的`package.json`中添加`lastPublish`字段,用来记录发布的时间。这是下一次发布时进行自动比对发布的依据。
|
||||
|
||||
|
||||
## 第二步:配置工作区发布脚本
|
||||
|
||||
在当前工程的根文件夹下配置`package.json`
|
||||
```json
|
||||
{
|
||||
"scripts":{
|
||||
"list:package": "node ./packages/autopublish/index.js list",
|
||||
"autopublish": "node ./packages/autopublish/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@voerkai18n/autopublish": "workspace:^1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
## 第三步:自动发布所有包
|
||||
|
||||
```javascript
|
||||
> pnpm autopublish -- -a -n
|
||||
```
|
||||
|
||||
`pnpm autopublish`会自动枚举出当前所有包,然后对比包路径`packages/<包名>`最后修改时间和`package.json`的`lastPublish`字段值,如果:
|
||||
- 最后修改时间大于最后发布时间,则发布该包
|
||||
- 最后修改时间关于或者小于最后发布时间,则忽略发布该包
|
||||
|
||||
因此,每次当修改完工程后,可以自动执行`pnpm autopublish -- -a -n`就可以进行全自动发布。
|
||||
|
||||
`pnpm autopublish`命令行参数:
|
||||
```shell
|
||||
自动发布包
|
||||
|
||||
Options:
|
||||
-a, --all 发布所有包
|
||||
-n, --no-ask 不询问
|
||||
-s, --no-silent 静默显示脚本输出
|
||||
-i, --version-increment-step [value] 版本增长方式 (choices: "major", "minor", "patch", "premajor", "preminor", "prepatch",
|
||||
"prerelease", default: "patch")
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
list 列出各个包的最后一次提交时间和版本信息
|
||||
```
|
||||
|
||||
- `-a`代表要发布所有包,如果没有启用`-n`,则会让用户选择要发布哪一个包。如果启用`-n`参数,则会全自动比对发布时间和修改时间后发布。
|
||||
- `-no-ask`代表不会询问让用户选择要发布的包.
|
||||
- `--no-silent`代表是否不输出脚本输出。
|
||||
- 由于包之间存在依赖关系,`autopublish`会根据依赖关系进行排序发布和关联发布。比如`@voerkai18n/cli`依赖于`@voerkai18n/utils`,当`@voerkai18n/utils`有更新需要发布时,`@voerkai18n/cli`也会自动发布。
|
||||
|
||||
|
||||
## 第四步: 手动选择发布
|
||||
|
||||
`pnpm autopublish -- -a -n`会根据发布时间和修改时间进行自动发布。也支持手动选择要发布的包。
|
||||
```javascript
|
||||
> pnpm autopublish -- -a
|
||||
````
|
||||
如果不启用`-n`参数,则会列出当前工作区的所有包,让用户选择要发布的包。
|
||||
|
||||
## 列出包
|
||||
|
||||
`pnpm autopublish -- list`列出当前工程的所有包,并显示当前包最近更新和最近发布时间。
|
@ -1,24 +0,0 @@
|
||||
const shelljs = require("shelljs");
|
||||
const path = require("path")
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检测当前工程是否是git工程
|
||||
*/
|
||||
function isGitRepo(){
|
||||
return shelljs.exec("git status", {silent: true}).code === 0;
|
||||
}
|
||||
|
||||
|
||||
function getProjectName(){
|
||||
const pkgFile = path.join(process.cwd(), "package.json")
|
||||
const pkg = fs.readJSONSync(pkgFile);
|
||||
return pkg.name;
|
||||
}
|
||||
|
||||
|
||||
module.exports ={
|
||||
isMonorepo,
|
||||
isGitRepo
|
||||
}
|
1
packages/react/index.d.ts
vendored
1
packages/react/index.d.ts
vendored
@ -4,6 +4,7 @@ import type React from "react"
|
||||
export type useVoerkaI18n = ()=>{
|
||||
language:string
|
||||
changeLanguage:(newLanguage:string)=>Promise<void>
|
||||
defaultLanguage:string, // 默认语言
|
||||
languages:VoerkaI18nSupportedLanguages,
|
||||
t:VoerkaI18nTranslate
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import React, { useState, useEffect,useContext,useCallback} from 'react';
|
||||
export const VoerkaI18nContext = React.createContext({
|
||||
languages:null,
|
||||
language:'zh',
|
||||
defaultLanguage:null,
|
||||
changeLanguage:() => {},
|
||||
t:()=>{}
|
||||
})
|
||||
@ -30,6 +31,7 @@ export function VoerkaI18nProvider(props){
|
||||
<VoerkaI18nContext.Provider value={{
|
||||
language,
|
||||
changeLanguage,
|
||||
defaultLanguage:VoerkaI18n.defaultLanguage,
|
||||
languages:VoerkaI18n.languages,
|
||||
t:scope.t
|
||||
}}>
|
||||
|
@ -59,11 +59,12 @@ const importTRegex = /^[^\w\r\n]*import\s*\{(.*)\bt\b(.*)\}\sfrom/gm
|
||||
|
||||
function replaceCode(code, idmap) {
|
||||
return code.replaceAll(TranslateRegex, (message) => {
|
||||
if(message in idmap) {
|
||||
return idmap[message]
|
||||
}else{
|
||||
return message
|
||||
}
|
||||
if(message in idmap) {
|
||||
return idmap[message]
|
||||
}else{
|
||||
const msg = unescape(message.replaceAll("\\u","%u"))
|
||||
return msg in idmap ? idmap[msg] : message
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -94,6 +95,7 @@ module.exports = function VoerkaI18nPlugin(opts={}) {
|
||||
/\.vue(\?.*)?/, // 所有vue文件
|
||||
"!(?<!.jsx\?.*).(css|json|scss|less|sass)$",
|
||||
/\.jsx(\?.*)?/,
|
||||
/\.ts(\?.*)?/,
|
||||
] // 提取范围
|
||||
},opts)
|
||||
|
||||
@ -103,7 +105,7 @@ module.exports = function VoerkaI18nPlugin(opts={}) {
|
||||
let languageFolder = getProjectLanguageFolder(projectRoot)
|
||||
|
||||
if(!fs.existsSync(languageFolder)){
|
||||
console.warn(`Voerkai18n语言文件夹不存在,@voerkai18n/vite未启用`)
|
||||
console.warn(`Voerkai18n语言文件夹不存在,@voerkai18n/vite未启用`)
|
||||
}
|
||||
if(debug){
|
||||
console.log("Project root: ",projectRoot)
|
||||
@ -141,8 +143,9 @@ module.exports = function VoerkaI18nPlugin(opts={}) {
|
||||
importSource = "./" + importSource
|
||||
}
|
||||
importSource=importSource.replace("\\","/")
|
||||
const extName = path.extname(id)
|
||||
// 转换Vue文件
|
||||
if(path.extname(id)==".vue"){
|
||||
if(extName==".vue"){
|
||||
// 优先在<script setup></script>中导入
|
||||
const setupScriptRegex = /(^\s*\<script.*\s*setup\s*.*\>)/gmi
|
||||
if(setupScriptRegex.test(code)){
|
||||
@ -150,7 +153,7 @@ module.exports = function VoerkaI18nPlugin(opts={}) {
|
||||
}else{// 如果没有<script setup>则在<script></script>中导入
|
||||
code = code.replace(/(^\s*\<script.*\>)/gmi,`$1\nimport { t } from '${importSource}';`)
|
||||
}
|
||||
}else{// 普通js文件需要添加到最前面
|
||||
}else if(['.js','.ts'].includes(extName)){// 普通js/ts文件需要添加到最前面
|
||||
code = code = `import { t } from '${importSource}';\n${code}`
|
||||
}
|
||||
}
|
||||
|
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -229,6 +229,7 @@ importers:
|
||||
rollup-plugin-clear: ^2.0.7
|
||||
rollup-plugin-terser: ^7.0.2
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.9
|
||||
'@babel/runtime-corejs3': 7.20.7
|
||||
core-js: 3.21.1
|
||||
devDependencies:
|
||||
@ -236,7 +237,6 @@ importers:
|
||||
'@babel/core': 7.18.10
|
||||
'@babel/plugin-transform-runtime': 7.18.10_@babel+core@7.18.10
|
||||
'@babel/preset-env': 7.18.10_@babel+core@7.18.10
|
||||
'@babel/runtime': 7.18.9
|
||||
'@rollup/plugin-babel': 5.3.1_tui6liyexu3zy4m5r2rytc7ixu
|
||||
'@rollup/plugin-commonjs': 21.1.0_rollup@2.77.2
|
||||
'@rollup/plugin-node-resolve': 13.3.0_rollup@2.77.2
|
||||
@ -1504,7 +1504,6 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.9
|
||||
dev: true
|
||||
|
||||
/@babel/standalone/7.18.10:
|
||||
resolution: {integrity: sha512-0KWHiRX9TUHiWE+dKYYEOIiRJcPwGU6u8Bq/p+ldekj7Kew9PCwl4S4FTSEPpTrn3Vc+r3iRSaN1l9AcGgLx4Q==}
|
||||
|
Loading…
x
Reference in New Issue
Block a user