React组件搭建(二)思路分享
简介
Nx新建自定义Plugin总览
实现目标:
- 自定义代码生成器:在你的项目,快速生成你的自定义代码片段
- 自动添加依赖:为你的项目自动安装Taiwindcss
- 一行命令推送你的代码到NPM
思路步骤:
- 通过NX Console快速初始化插件
- 参考和略读@nrwl/react部分源码,结合自己的需求快速自定义插件
- 发布Npm部分暂时使用社区插件ngx-deploy-npm
笔者现有环境配置说明
- win10系统,Nvm-node,pnpm,淘宝镜像源
- 编辑器-Vscode
扩展官方插件
新建插件
- 安装官方插件助手
- 在lib下新建插件库
- 在插件库下新建一个生成器
具体命令:
pnpm add -D @nrwl/nx-plugin
nx generate @nrwl/nx-plugin:plugin nx-plugin-ui --importPath=@taibui/devkit
nx generate @nrwl/nx-plugin:generator library --project=nx-plugin-ui
> NX Running global Nx CLI with PNPM may have issues.
> NX Generating @nrwl/nx-plugin:generator
CREATE libs/nx-plugin-ui/src/generators/library/files/src/index.ts__template__
CREATE libs/nx-plugin-ui/src/generators/library/generator.spec.ts
CREATE libs/nx-plugin-ui/src/generators/library/generator.ts
CREATE libs/nx-plugin-ui/src/generators/library/schema.d.ts
CREATE libs/nx-plugin-ui/src/generators/library/schema.json
UPDATE libs/nx-plugin-ui/generators.json
定义插件shema
在刚创建的生成器-library下定义schema
具体思路
1.扩展自@newl/react。只需复制官方插件的就好了 2.我们的自定义需求仅需在impl中实现,只需小幅度定义schema
功能拆分和实现
1。基于官方插件的,使用库生成器生成一个库 2. 为了调试和初始化,使用组件生成器生成一个Test组件(第一个生成器也在src根目录生成了组件) 3. 自定义需求:改进官方插件的配置Storybook后的工作流(在storybook配置文件引入css)
自定义需求实现思路
创建一个React库
只需调用官方维护的React插件,传入符合个人需求的参数,案例是限定rollup打包和移除默认生成的组件,强制可发布
import {
libraryGenerator,
...
} from '@nrwl/react';
const initLibrary = await libraryGenerator(tree, {
...options,
unitTestRunner: 'jest',
bundler: 'rollup',
publishable: true,
buildable: true,
component: false,
});
react组件生成器
在项目初始化的时候生成一个Test组件,默认官方是在入口文件同级目录生成一个组件库同名的组件
const genComponent = await componentGenerator(tree, {
name: 'Test',
project: options.name,
style: 'none',
//默认导出入口文件
export: true,
});
tips:实际使用主要也是以生成文件为主,自定义组件生成器实现很简单,考虑到可以另开一偏转讲,案例只使用官方默认的
可以先大致了解,以下是官方的模版文件
主要是使用generateFiles
,略微改动就能创建自定义组件生成器
storybook配置
限定webpack打包。默认配置文件为ts,不新建测试app
如果配置为ts,js摇摆,可能要做些兼容处理,因为后期引入css文件要修改配置文件
const setStorybookForLib = await storybookConfigurationGenerator(tree, {
name: options.name,
configureCypress: false,
// Automatically generate *.stories.ts files for components declared in this project
generateStories: true,
generateCypressSpecs: false,
bundler: 'webpack',
tsConfiguration: true,
});
// 配置文件为ts和js的区别
// 更新 ./storybook/previews.ts 或者 ./.storybook/preview.js
const storybookConfigPath = joinPathFragments(
projectRoot,
options.storyTsConfiguration
? '.storybook/preview.ts'
: '.storybook/preview.js'
);
const storybookConfigSource = host.read(storybookConfigPath, 'utf-8');
if (storybookConfigSource !== null && cssSource !== null) {
const changes = applyChangesToString(storybookConfigSource, [
{
type: ChangeType.Insert,
index: 0,
text: `import '../src/styles.css';\n`,
},
]);
host.write(storybookConfigPath, changes);
}
配置tailwindcss
因为是基于官方的插件修改,这里有个坑,插件会检测你的目录是否有css样式文件。而我们配置组件库时,的确没有css文件。使用tailwind,也就是在打包时在入口文件引入。如果不先处理,也不影响本地开发(前提配置好Storybook),仅仅是友好提醒你没有配置好tailwind
// 在配置tailwindcss之前,先在项目src目录下创建一个styles.css文件
createFiles(tree, normOptions);
// 正常为lib配置tailwindcss
const setTaikwindForLib = await setupTailwindGenerator(tree, {
project: options.name,
// The name of the target used to build the project. This option is not needed in most cases
buildTarget: `${options.name}-build`,
});
// 官方插件部分源码
const knownLocations = [
// Plain React
'src/styles.css',
'src/styles.scss',
'src/styles.styl',
'src/styles.less',
// Next.js
'pages/styles.css',
'pages/styles.scss',
'pages/styles.styl',
'pages/styles.less',
];
if (stylesPath) {
const content = tree.read(stylesPath).toString();
tree.write(
stylesPath,
`@tailwind base;\n@tailwind components;\n@tailwind utilities;\n${content}`
);
} else {
logger.warn(
stripIndents`
Could not find stylesheet to update. Add the following imports to your stylesheet (e.g. styles.css):
@tailwind base;
@tailwind components;
@tailwind utilities;
更新文件
应用配置tailwindcss理论上插件会帮你引入tailwind样式,但是15.8的nx插件还是不能直接配置taiwind
主要思路,Nx修改更新json文件是很容易的(提供助手函数updateJson
),但是修改ts,js文件,基本要靠自己实现,稍微复杂的,比如添加一个路由组件,自动更新路由配置,估计就要上AST了。
案例只是修改入口文件,基本上只是在第一行 或者最后一行插入字符,
// 更新文件
addExportsToBarrel(tree, normOptions);
export default function addExportsToBarrel(
host: Tree,
options: NormalizedSchema
) {
if (!tsModule) {
tsModule = ensureTypescript();
}
const project = getProjects(host).get(options.name);
const {
sourceRoot: projectSourceRoot,
projectType,
root: projectRoot,
} = project;
const isApp = projectType === 'application';
const indexFilePath = joinPathFragments(projectSourceRoot, 'index.ts');
const indexSource = host.read(indexFilePath, 'utf-8');
if (!isApp) {
const cssFilePath = joinPathFragments(projectSourceRoot, 'styles.css');
const cssSource = host.read(cssFilePath, 'utf-8');
if (indexSource !== null && cssSource !== null) {
const changes = applyChangesToString(indexSource, [
{
type: ChangeType.Insert,
index: 0,
text: `import './styles.css';\n`,
},
]);
host.write(indexFilePath, changes);
}
新建Rollup和更新project文件
通过generateFiles
新建文件,要注意的是,要更新Nx程式级 | 库级的project.json
案例修改的是执行器的配置
你的Nx命令都注册在该配置文件上
tips 旧版本的nx会在根目录有个workspace.json,新版本现在已经废弃了,用project.json取代它的功能
// 新建 custom-rollup.config.js
generateFiles(
tree,
joinPathFragments(__dirname, 'files/rollup/'),
joinPathFragments(normOptions.projectRoot),
{
template: '',
}
);
// 更新project.json
updateProject(tree, normOptions);
export default function updateProject(tree: Tree, options: NormalizedSchema) {
const project = readProjectConfiguration(tree, options.name);
project.targets = project.targets || {};
project.targets.build = {
...project.targets.build,
options: {
...project.targets.build.options,
rollupConfig: `${options.projectRoot}/custom-rollup.config.js`,
},
};
updateProjectConfiguration(tree, options.name, project);
}
tailwind其它配置
tailwind的prettier插件
安装插件依赖 等同于执行 yarn add prettier-plugin-tailwindcss
案例选择的是删掉原有配置文件,因为修改js | ts 略复杂,初始化项目也没有什么配置,上AST不如新建
const addPrettierTailwindPlugin = addDependenciesToPackageJson(
tree,
{},
{
'prettier-plugin-tailwindcss': '^0.2.7',
}
);
// 删除原有的tailwindcss配置文件
try {
tree.exists(`${normOptions.projectRoot}/tailwind.config.js`) &&
tree.delete(`${normOptions.projectRoot}/tailwind.config.js`);
} catch (e) {
throw new Error('删除原有的tailwindcss配置文件失败');
}
新建两个tailwind配置文件
工作区级和程式级的配置文件
// 新建tailwind.config.js
generateFiles(
tree,
joinPathFragments(__dirname, 'files/taiwind/'),
joinPathFragments(normOptions.projectRoot),
{
template: '',
configRelativeUrl: offsetFromRoot(normOptions.projectRoot),
}
);
// 检查工作区是否存在配置文件taiwind-worksapce-preset.config.js,否则创建
if (
!tree.exists(
joinPathFragments(tree.root, 'taiwind-worksapce-preset.config.js')
)
) {
generateFiles(
tree,
joinPathFragments(__dirname, 'files/twpreset/'),
joinPathFragments(workspaceRoot),
{
template: '',
}
);
logger.warn("Don't forget to check the preset to your tailwind.config.js");
}
这里借助offsetFromRoot(normOptions.projectRoot)
获取两个文件的相对位置,这样程式级配置文件才能引用对
module.exports = {
presets: [require('<%= configRelativeUrl %>taiwind-worksapce-preset.config.js')],
content: [
...
],
...
plugins: [],
};
开发经验
通过--dry-run 不断调试,总能完成自定义插件
pnpm exec nx g @taibui/devkit --dry-run
总结
Nx暴露的许多助手函数,能让我们十分容易的在工作区管理文件,社区活跃。目前底层是TS,但是有计划迁移其它方案,目前.net 和 Go也能脱离NPM使用Nx,接个人私活,算是一个不错的工具。