@coraltravelcenter/b2c-landing-builder 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -76,13 +76,14 @@ export default {
76
76
 
77
77
  Поддерживаются:
78
78
 
79
- - JavaScript: `vanilla`, `vue`;
79
+ - JavaScript: `js`, `ts`, `vue`, `vue-ts` (`vanilla` поддерживается для старых проектов);
80
80
  - разметка: `html`, `pug`;
81
81
  - стили: `css`, `scss`, `less`;
82
82
  - сайты: `coral`, `sunmar`.
83
83
 
84
- Зависимости выбранного стека устанавливаются локально в проект: `vue` и
85
- `@vitejs/plugin-vue` для Vue, `pug` для Pug, `less` для Less и `sass` для SCSS.
84
+ Зависимости выбранного стека устанавливаются локально в проект: `typescript`
85
+ для TS, `vue` и `@vitejs/plugin-vue` для Vue, `pug` для Pug, `less` для Less и
86
+ `sass` для SCSS.
86
87
  Builder загружает Vue-плагин из проекта, поэтому Vue-зависимости не
87
88
  устанавливаются вместе с глобальным builder.
88
89
 
@@ -105,7 +106,7 @@ src/
105
106
  styles/
106
107
  welcome.css | welcome.scss | welcome.less
107
108
  scripts/
108
- welcome.js
109
+ welcome.js | welcome.ts
109
110
  components/ # только для Vue
110
111
  public/
111
112
  pug.rc # только для Pug
@@ -125,13 +126,13 @@ package.json
125
126
  }
126
127
  ```
127
128
 
128
- Для каждого ключа builder ищет разметку, стиль и необязательный JS-файл с тем
129
+ Для каждого ключа builder ищет разметку, стиль и необязательный JS/TS-файл с тем
129
130
  же именем. Форматы определяются `landing.config.mjs`.
130
131
 
131
- ## Контракт JavaScript
132
+ ## Контракт JavaScript и TypeScript
132
133
 
133
- Если у блока есть `src/scripts/<key>.js`, файл обязан экспортировать функцию
134
- инициализации по умолчанию:
134
+ Если у блока есть `src/scripts/<key>.js` или `src/scripts/<key>.ts`, файл обязан
135
+ экспортировать функцию инициализации по умолчанию:
135
136
 
136
137
  ```js
137
138
  export default function hero() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coraltravelcenter/b2c-landing-builder",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "CLI and build toolkit for B2C landing projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import {assertBlockKey, readOrder, writeOrder} from "./utils.mjs";
5
+ import {resolveScriptStack} from "../config/script-stack.mjs";
5
6
 
6
7
  function markupTemplate(key, format) {
7
8
  return format === "pug" ? `section.${key}\n` : `<section class="${key}">\n</section>\n`;
@@ -21,10 +22,11 @@ export function addBlock(key, config, root = process.cwd()) {
21
22
  const {order, orderPath} = readOrder(root);
22
23
  if (order.blocks.includes(key)) throw new Error(`Block already exists in src/order.json: ${key}`);
23
24
 
25
+ const scriptStack = resolveScriptStack(config.stack.script);
24
26
  const files = [
25
27
  [path.resolve(root, "src/markup", `${key}.${config.stack.markup}`), markupTemplate(key, config.stack.markup)],
26
28
  [path.resolve(root, "src/styles", `${key}.${config.stack.styles}`), styleTemplate(key)],
27
- [path.resolve(root, "src/scripts", `${key}.js`), scriptTemplate(key)],
29
+ [path.resolve(root, "src/scripts", `${key}.${scriptStack.extension}`), scriptTemplate(key)],
28
30
  ];
29
31
  const collision = files.find(([filePath]) => fs.existsSync(filePath));
30
32
  if (collision) throw new Error(`Cannot add block; target already exists: ${path.relative(root, collision[0])}`);
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
 
4
4
  import {assertBlockKey, readOrder, writeOrder} from "./utils.mjs";
5
5
 
6
- const FILES = {markup: ["html", "pug"], styles: ["css", "scss", "less"], scripts: ["js"]};
6
+ const FILES = {markup: ["html", "pug"], styles: ["css", "scss", "less"], scripts: ["js", "ts"]};
7
7
 
8
8
  export function renameBlock(fromKey, toKey, root = process.cwd()) {
9
9
  assertBlockKey(fromKey, "source block key");
@@ -0,0 +1,19 @@
1
+ const SCRIPT_STACKS = {
2
+ vanilla: {extension: "js", vue: false, dependencies: []},
3
+ js: {extension: "js", vue: false, dependencies: []},
4
+ ts: {extension: "ts", vue: false, dependencies: ["typescript"]},
5
+ vue: {extension: "js", vue: true, dependencies: ["vue", "@vitejs/plugin-vue"]},
6
+ "vue-ts": {
7
+ extension: "ts",
8
+ vue: true,
9
+ dependencies: ["vue", "@vitejs/plugin-vue", "typescript"],
10
+ },
11
+ };
12
+
13
+ export const SCRIPT_STACK_VALUES = new Set(Object.keys(SCRIPT_STACKS));
14
+
15
+ export function resolveScriptStack(script) {
16
+ const stack = SCRIPT_STACKS[script];
17
+ if (!stack) throw new Error(`Unsupported stack.script: ${JSON.stringify(script)}`);
18
+ return stack;
19
+ }
@@ -1,7 +1,9 @@
1
+ import {SCRIPT_STACK_VALUES} from "./script-stack.mjs";
2
+
1
3
  const PROJECT_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
2
4
  const SUPPORTED_SCHEMA_VERSIONS = new Set([1]);
3
5
  const STACK_VALUES = {
4
- script: new Set(["vanilla", "vue"]),
6
+ script: SCRIPT_STACK_VALUES,
5
7
  markup: new Set(["html", "pug"]),
6
8
  styles: new Set(["css", "scss", "less"]),
7
9
  };
@@ -2,18 +2,22 @@ import fs from "node:fs";
2
2
  import {createRequire} from "node:module";
3
3
  import path from "node:path";
4
4
 
5
+ import {resolveScriptStack} from "./script-stack.mjs";
6
+
5
7
  const BLOCK_KEY_RE = /^[a-z0-9][a-z0-9-_]*$/i;
6
8
  const DEFAULT_EXPORT_RE = /\bexport\s+default\s+(?:async\s+)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>|[A-Za-z_$][\w$]*\s*;?)/;
7
9
  const STACK_DEPENDENCIES = {
8
- script: {vue: ["vue", "@vitejs/plugin-vue"]},
9
10
  markup: {pug: ["pug"]},
10
11
  styles: {less: ["less"], scss: ["sass"]},
11
12
  };
12
13
 
13
14
  function validateLocalDependencies(config, root) {
14
15
  const projectRequire = createRequire(path.resolve(root, "package.json"));
15
- const dependencies = Object.entries(STACK_DEPENDENCIES)
16
- .flatMap(([section, values]) => values[config.stack[section]] || []);
16
+ const dependencies = [
17
+ ...resolveScriptStack(config.stack.script).dependencies,
18
+ ...Object.entries(STACK_DEPENDENCIES)
19
+ .flatMap(([section, values]) => values[config.stack[section]] || []),
20
+ ];
17
21
 
18
22
  for (const dependency of dependencies) {
19
23
  try {
@@ -42,6 +46,7 @@ function readJson(filePath, label) {
42
46
  }
43
47
 
44
48
  export function validateProject(config, root = process.cwd()) {
49
+ const scriptStack = resolveScriptStack(config.stack.script);
45
50
  const orderPath = path.resolve(root, "src/order.json");
46
51
  const order = readJson(orderPath, "src/order.json");
47
52
  if (!Array.isArray(order.blocks)) {
@@ -61,12 +66,12 @@ export function validateProject(config, root = process.cwd()) {
61
66
  throw new Error(`Missing configured markup for block ${key}: src/markup/${key}.${config.stack.markup}`);
62
67
  }
63
68
 
64
- const scriptPath = path.resolve(root, "src/scripts", `${key}.js`);
69
+ const scriptPath = path.resolve(root, "src/scripts", `${key}.${scriptStack.extension}`);
65
70
  if (!fs.existsSync(scriptPath)) continue;
66
71
  const source = fs.readFileSync(scriptPath, "utf8");
67
72
  if (!DEFAULT_EXPORT_RE.test(source)) {
68
73
  throw new Error(
69
- `src/scripts/${key}.js must export its initializer as default function`
74
+ `src/scripts/${key}.${scriptStack.extension} must export its initializer as default function`
70
75
  );
71
76
  }
72
77
  }
@@ -6,6 +6,7 @@ import {cleanDir, ensureDir, existsFile, normalizeHtml, r, readBlocks} from "./_
6
6
  import {loadProjectVuePlugin} from "./loadProjectVuePlugin.mjs";
7
7
  import {processMarkup} from "./processMarkup.mjs";
8
8
  import {renderPugFile} from "./pugMarkup.mjs";
9
+ import {resolveScriptStack} from "../config/script-stack.mjs";
9
10
 
10
11
  const ORDER_FILE = r("src/order.json");
11
12
 
@@ -83,7 +84,7 @@ try { if (typeof init === "function") init(); } catch (e) { console.warn(e); }
83
84
  },
84
85
  };
85
86
 
86
- const vue = stack.script === "vue"
87
+ const vue = resolveScriptStack(stack.script).vue
87
88
  ? await loadProjectVuePlugin(process.cwd())
88
89
  : null;
89
90
  const res = await build({
@@ -120,11 +121,12 @@ try { if (typeof init === "function") init(); } catch (e) { console.warn(e); }
120
121
  // ---------- block build ----------
121
122
 
122
123
  async function buildBlock(key, stack) {
124
+ const scriptStack = resolveScriptStack(stack.script);
123
125
  const markupPath = path.join(MARKUP_DIR, `${key}.${stack.markup}`);
124
126
  if (!markupPath) return null;
125
127
  if (!existsFile(markupPath)) return null;
126
128
 
127
- const jsPath = path.join(SCRIPTS_DIR, `${key}.js`);
129
+ const jsPath = path.join(SCRIPTS_DIR, `${key}.${scriptStack.extension}`);
128
130
 
129
131
  const markup = stack.markup === "pug"
130
132
  ? renderPugFile(markupPath)
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import {existsFile, normalizeHtml, readBlocks} from "./_utils.mjs";
5
5
  import {processMarkup} from "./processMarkup.mjs";
6
6
  import {getPugConfigFile, renderPugFile} from "./pugMarkup.mjs";
7
+ import {resolveScriptStack} from "../config/script-stack.mjs";
7
8
 
8
9
  const VIRTUAL_ID = "virtual:landing-blocks";
9
10
  const RESOLVED_VIRTUAL_ID = `\0${VIRTUAL_ID}`;
@@ -19,6 +20,7 @@ function isInside(directory, filePath) {
19
20
  }
20
21
 
21
22
  export function landingBlocksPlugin({root = process.cwd(), stack} = {}) {
23
+ const scriptStack = resolveScriptStack(stack.script);
22
24
  const orderFile = path.resolve(root, "src/order.json");
23
25
  const markupDir = path.resolve(root, "src/markup");
24
26
  const stylesDir = path.resolve(root, "src/styles");
@@ -87,7 +89,7 @@ export function landingBlocksPlugin({root = process.cwd(), stack} = {}) {
87
89
  imports.push(`import ${JSON.stringify(toVitePath(stylePath))};`);
88
90
  }
89
91
 
90
- const scriptPath = path.join(scriptsDir, `${key}.js`);
92
+ const scriptPath = path.join(scriptsDir, `${key}.${scriptStack.extension}`);
91
93
  let init = "null";
92
94
  if (existsFile(scriptPath)) {
93
95
  this.addWatchFile(scriptPath);
@@ -2,22 +2,37 @@ import {fileURLToPath} from "node:url";
2
2
  import path from "node:path";
3
3
 
4
4
  import {createServer} from "vite";
5
- import monkey from "vite-plugin-monkey";
6
5
 
7
6
  import {loadConfig} from "../config/load-config.mjs";
8
7
  import {resolvePreset} from "../config/resolve-preset.mjs";
9
8
  import {landingBlocksPlugin} from "../lib/landingBlocksPlugin.mjs";
10
9
  import {loadProjectVuePlugin} from "../lib/loadProjectVuePlugin.mjs";
10
+ import {resolveScriptStack} from "../config/script-stack.mjs";
11
11
 
12
12
  const clientEntry = fileURLToPath(new URL("./client.js", import.meta.url));
13
13
  const builderRoot = fileURLToPath(new URL("../../", import.meta.url));
14
14
  const clientEntryUrl = `/@fs${clientEntry.replaceAll("\\", "/")}`;
15
+ let monkeyPlugin;
16
+
17
+ async function loadMonkeyPlugin() {
18
+ if (monkeyPlugin) return monkeyPlugin;
19
+
20
+ const projectRoot = process.cwd();
21
+ try {
22
+ process.chdir(builderRoot);
23
+ monkeyPlugin = (await import("vite-plugin-monkey")).default;
24
+ return monkeyPlugin;
25
+ } finally {
26
+ process.chdir(projectRoot);
27
+ }
28
+ }
15
29
 
16
30
  export async function createViteConfig(root = process.cwd()) {
17
31
  const config = await loadConfig(root);
18
32
  const preset = resolvePreset(config.site.preset);
33
+ const monkey = await loadMonkeyPlugin();
19
34
  const shouldOpen = process.env.B2C_NO_OPEN !== "1";
20
- const vue = config.stack.script === "vue"
35
+ const vue = resolveScriptStack(config.stack.script).vue
21
36
  ? await loadProjectVuePlugin(root)
22
37
  : null;
23
38
 
@@ -26,7 +41,7 @@ export async function createViteConfig(root = process.cwd()) {
26
41
  configFile: false,
27
42
  server: {
28
43
  host: "127.0.0.1",
29
- port: 5173,
44
+ port: Number(process.env.B2C_PORT || 5173),
30
45
  strictPort: true,
31
46
  cors: true,
32
47
  open: shouldOpen ? "/__vite-plugin-monkey.install.user.js" : false,