Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: custom component name should be constrained explicitely - WF-52 #550

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion alfred/npm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

@alfred.command("npm.lint", help="lint check npm packages")
def npm_lint():
alfred.run("npm run ui:lint:ci")
alfred.run("npm run ui:lint.ci")
alfred.run("npm run ui:custom.check")

@alfred.command("npm.e2e", help="run e2e tests")
@alfred.option('--browser', '-b', help="run e2e tests on specified browser", default='chromium')
Expand Down
9 changes: 7 additions & 2 deletions docs/framework/custom-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,18 @@ A single or multiple templates can be specified. Take into account that they wil

## Bundling templates

Execute `npm run custom.build`, this will generate the output `.js` and `.css` files into `ui/custom_components_dist`.
Execute `npm run custom.build` into `src/ui`, this will generate the output `.js` and `.css` files into `./custom_components_dist`.

```sh
# "build" builds the entire front-end
# "custom.build" only builds the custom templates

npm run custom.check # Optional: checks certain issues on custom components
npm run custom.build
```

Collect the files from `ui/custom_components_dist` and pack them in a folder such as `my_custom_bubbles`. The folder containing the generated files, e.g. `my_custom_bubbles`, can now be placed in the `extensions/` folder of any Framework project. It'll be automatically detected during server startup.
Collect the files from `./custom_components_dist` and pack them in a folder such as `my_custom_bubbles`. The folder containing the generated files, e.g. `my_custom_bubbles`, can now be placed in the `extensions/` folder of any Framework project. It'll be automatically detected during server startup.

<Tip>
The `custom.check` command is optional, but it's recommended to run it before building the custom components. It checks for common issues in the custom components, such as invalid key declaration, ...
</Tip>
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
"ui:build": "npm run -w writer-ui build",
"ui:preview": "npm run -w writer-ui preview",
"ui:custom.build": "npm run -w writer-ui custom.build",
"ui:custom.check": "npm run -w writer-ui custom.check",
"ui:lint": "npm run -w writer-ui lint",
"ui:lint:ci": "npm run -w writer-ui lint:ci",
"ui:lint.ci": "npm run -w writer-ui lint.ci",

"docs:codegen": "npm run -w writer-docs codegen",
"docs:preview": "npm run -w writer-docs preview",
Expand Down
5 changes: 3 additions & 2 deletions src/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
"build": "vite build",
"preview": "vite preview --port 5050",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path ../../.gitignore --ignore-path .gitignore",
"lint:ci": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --ignore-path ../../.gitignore --ignore-path .gitignore",
"lint.ci": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --ignore-path ../../.gitignore --ignore-path .gitignore",
"codegen": "node tools/generator.mjs",
"custom.dev": "vite --config vite.config.custom.ts",
"custom.build": "vite build --config vite.config.custom.ts",
"custom.check": "node tools/custom_check.mjs",
"storybook": "storybook dev -p 6006",
"storybook.build": "storybook build"
},
Expand Down Expand Up @@ -61,4 +62,4 @@
"eslint-plugin-storybook": "0.8.0",
"storybook": "8.0.5"
}
}
}
27 changes: 25 additions & 2 deletions src/ui/src/core/loadExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,33 @@ async function importCustomComponentTemplate(path: string) {
await import(/* @vite-ignore */ getRelativeExtensionsPath() + path);
Object.entries(window[CUSTOM_COMPONENTS_GLOBAL_VAR])?.forEach(
([key, template]) => {
console.log(`Registering template for "${key}".`);
registerComponentTemplate(`custom_${key}`, template);
if (checkComponentKey(key)) {
registerComponentTemplate(`custom_${key}`, template);
console.log(`Registering template for "${key}".`);
} else {
console.warn(
`custom component '${key}' is ignored. A custom component should be declared using only alphanumeric lowercase and _.`,
);
}
},
);
}

/**
* Checks that the key contains only alphanumeric characters and underscores without capital letters
* The clipboard api use in drag and drop doesn't handle uppercase.
*
* mycomponent : valid
* myComponent : invalid
* myCOMPONENT : invalid
*
* @see https://github.com/writer/writer-framework/issues/517
*/
export function checkComponentKey(key: string): boolean {
const isValidKey = /^[a-z0-9_]+$/.test(key);
return isValidKey;
}

function loadStylesheet(path: string) {
const el: HTMLLinkElement = document.createElement("link");
el.rel = "stylesheet";
Expand All @@ -41,3 +62,5 @@ function getRelativeExtensionsPath() {

return `${pathname}extensions/`;
}


18 changes: 18 additions & 0 deletions src/ui/tools/core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,21 @@ export async function loadComponents() {

return data;
}

/**
* imports a vue-dependent module.
*/
export async function importVue(modulePath) {
const vite = await createServer({
includeWriterComponentPath: true,
server: {
middlewareMode: true,
},
appType: "custom",
});

const m = await vite.ssrLoadModule(path.join(__dirname, modulePath));
await vite.close();

return m;
}
42 changes: 42 additions & 0 deletions src/ui/tools/custom_check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { importVue } from "./core.mjs";

async function checkDeclarationKey() {
let hasFailed = false;
const module = await importVue("../src/custom_components/index.ts");
const { checkComponentKey } = await importVue(
"../src/core/loadExtensions.ts",
);
const invalidCustomComponentKeys = [];
Object.keys(module.default).forEach((key) => {
if (!checkComponentKey(key)) {
invalidCustomComponentKeys.push(key);
hasFailed = true;
}
});

if (invalidCustomComponentKeys.length !== 0) {
// eslint-disable-next-line no-console
console.error(
`ERROR: Invalid component declaration: ${invalidCustomComponentKeys} into 'src/custom_components/index.ts'. Their key must be declared using only lowercase and alphanumeric characters.`,
);
}
return hasFailed;
}

/**
* Check the custom components in continuous integration
*
* npm run custom.check
*
*/
async function check() {
let hasFailed = false;

hasFailed |= await checkDeclarationKey();

if (hasFailed) {
process.exit(1);
}
}

check();
Loading