@ywal123456/jskim 0.1.1 → 0.3.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.
@@ -1,13 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { loadConfig } = require('../lib/load-config');
4
- const { resolveProject } = require('../lib/resolve-project');
5
- const { createProjectWatcher } = require('../lib/create-project-watcher');
6
- const { createStaticServer } = require('../lib/create-static-server');
7
- const { createLiveReload } = require('../lib/create-live-reload');
3
+ const { createWatchRuntime } = require('../lib/create-watch-runtime');
8
4
  const { registerShutdown } = require('./register-shutdown');
9
- const { toDisplayPath } = require('./path-display');
10
- const { formatListenError } = require('./serve-errors');
11
5
 
12
6
  /**
13
7
  * dev コマンドを実行します。
@@ -22,51 +16,15 @@ async function runDevCommand(options = {}) {
22
16
  const usageLine =
23
17
  options.usageLine || 'npm run dev -- <project-name>';
24
18
 
25
- const { config } = loadConfig(workspaceRoot);
26
- const project = resolveProject({
27
- config,
19
+ const runtime = createWatchRuntime({
20
+ mode: 'dev',
28
21
  workspaceRoot,
29
22
  projectName: options.projectName,
30
23
  commandName: 'dev',
31
24
  usageLine,
32
25
  });
33
26
 
34
- const host = project.serve.host.trim();
35
- const port = project.serve.port;
36
- const liveReloadEnabled = project.dev.liveReload;
37
- const outputDisplay = toDisplayPath(project.outputDir, workspaceRoot);
38
-
39
- const liveReload = createLiveReload({
40
- projectName: project.name,
41
- enabled: liveReloadEnabled,
42
- });
43
-
44
- const staticServer = createStaticServer({
45
- rootDir: project.outputDir,
46
- host,
47
- port,
48
- projectName: project.name,
49
- handleInternalRequest: (req, res, meta) =>
50
- liveReload.handleRequest(req, res, meta),
51
- transformHtml: liveReloadEnabled
52
- ? (html) => liveReload.injectHtml(html)
53
- : undefined,
54
- });
55
-
56
- const projectWatcher = createProjectWatcher(project, {
57
- runInitialBuild: true,
58
- logChanges: true,
59
- });
60
-
61
- // 成功した再ビルドのあとだけ reload(初回はクライアント未接続のため不要)
62
- projectWatcher.on('build:success', ({ initial }) => {
63
- if (!initial && liveReloadEnabled) {
64
- liveReload.broadcastReload();
65
- }
66
- });
67
-
68
27
  let stopping = false;
69
- let watcherStarted = false;
70
28
 
71
29
  async function shutdown() {
72
30
  if (stopping) {
@@ -75,21 +33,7 @@ async function runDevCommand(options = {}) {
75
33
  stopping = true;
76
34
 
77
35
  try {
78
- if (watcherStarted) {
79
- await projectWatcher.close();
80
- }
81
- } catch {
82
- // 終了時エラーは無視
83
- }
84
-
85
- try {
86
- liveReload.close();
87
- } catch {
88
- // 終了時エラーは無視
89
- }
90
-
91
- try {
92
- await staticServer.stop();
36
+ await runtime.close();
93
37
  } catch {
94
38
  // 終了時エラーは無視
95
39
  }
@@ -98,65 +42,22 @@ async function runDevCommand(options = {}) {
98
42
  process.exit(0);
99
43
  }
100
44
 
101
- async function failStartup(err) {
45
+ registerShutdown(shutdown);
46
+
47
+ try {
48
+ await runtime.start();
49
+ } catch (err) {
102
50
  const message = err && err.message ? err.message : String(err);
103
51
  console.error(message);
104
-
105
- try {
106
- if (watcherStarted) {
107
- await projectWatcher.close();
108
- }
109
- } catch {
110
- // ignore
111
- }
112
-
113
- try {
114
- liveReload.close();
115
- } catch {
116
- // ignore
117
- }
118
-
119
52
  try {
120
- await staticServer.stop();
53
+ await runtime.close();
121
54
  } catch {
122
55
  // ignore
123
56
  }
124
-
125
57
  // cleanup 完了後に終了する。
126
58
  // IPC 接続があると exitCode だけではプロセスが残るため明示終了する。
127
59
  process.exit(1);
128
60
  }
129
-
130
- registerShutdown(shutdown);
131
-
132
- // 初回 build → server → watcher
133
- await projectWatcher.start({ watchFiles: false });
134
- watcherStarted = true;
135
-
136
- try {
137
- await staticServer.start();
138
- } catch (err) {
139
- await failStartup(
140
- formatListenError(err, {
141
- projectName: project.name,
142
- host,
143
- port,
144
- kind: '開発',
145
- })
146
- );
147
- return;
148
- }
149
-
150
- await projectWatcher.startWatching();
151
-
152
- console.log('[JSKim] 開発サーバーを起動しました。');
153
- console.log(`プロジェクト: ${project.name}`);
154
- console.log(`ルート: ${outputDisplay}`);
155
- console.log(`URL: ${staticServer.url}`);
156
- console.log(
157
- `ライブリロード: ${liveReloadEnabled ? '有効' : '無効'}`
158
- );
159
- console.log('終了するには Ctrl+C を押してください。');
160
61
  }
161
62
 
162
63
  module.exports = {
@@ -1,8 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { loadConfig } = require('../lib/load-config');
4
- const { resolveProject } = require('../lib/resolve-project');
5
- const { createProjectWatcher } = require('../lib/create-project-watcher');
3
+ const { createWatchRuntime } = require('../lib/create-watch-runtime');
6
4
  const { registerShutdown } = require('./register-shutdown');
7
5
 
8
6
  /**
@@ -18,33 +16,14 @@ async function runWatchCommand(options = {}) {
18
16
  const usageLine =
19
17
  options.usageLine || 'npm run watch -- <project-name>';
20
18
 
21
- const { config } = loadConfig(workspaceRoot);
22
- const project = resolveProject({
23
- config,
19
+ const runtime = createWatchRuntime({
20
+ mode: 'watch',
24
21
  workspaceRoot,
25
22
  projectName: options.projectName,
26
23
  commandName: 'watch',
27
24
  usageLine,
28
25
  });
29
26
 
30
- const projectWatcher = createProjectWatcher(project, {
31
- runInitialBuild: true,
32
- logChanges: true,
33
- });
34
-
35
- projectWatcher.on('ready', ({ displayPaths, debounceMs }) => {
36
- console.log(`[JSKim] プロジェクトを監視しています: ${project.name}`);
37
- console.log('パス:');
38
- for (const display of displayPaths) {
39
- console.log(`- ${display}`);
40
- }
41
- console.log('');
42
- console.log(`Debounce: ${debounceMs}ms`);
43
- console.log('終了するには Ctrl+C を押してください。');
44
- });
45
-
46
- await projectWatcher.start();
47
-
48
27
  let stopping = false;
49
28
 
50
29
  async function shutdown() {
@@ -54,7 +33,7 @@ async function runWatchCommand(options = {}) {
54
33
  stopping = true;
55
34
 
56
35
  try {
57
- await projectWatcher.close();
36
+ await runtime.close();
58
37
  } catch {
59
38
  // 終了時エラーは無視
60
39
  }
@@ -64,6 +43,17 @@ async function runWatchCommand(options = {}) {
64
43
  }
65
44
 
66
45
  registerShutdown(shutdown);
46
+
47
+ try {
48
+ await runtime.start();
49
+ } catch (err) {
50
+ try {
51
+ await runtime.close();
52
+ } catch {
53
+ // ignore
54
+ }
55
+ throw err;
56
+ }
67
57
  }
68
58
 
69
59
  module.exports = {
@@ -7,23 +7,17 @@ const { createNunjucksEnv } = require('./create-nunjucks-env');
7
7
  const { cleanOutput } = require('./clean-output');
8
8
  const { renderPages } = require('./render-pages');
9
9
  const { copyFiles } = require('./copy-files');
10
+ const { processFiles } = require('./process-files');
10
11
 
11
12
  /**
12
13
  * 設定を読み込み、プロジェクトを解決してフルビルドを実行します。
13
14
  *
14
15
  * @param {string|undefined} projectName
15
16
  * @param {object} [options]
16
- * @param {string} [options.workspaceRoot]
17
- * @param {string} [options.commandName='build']
18
- * @param {string} [options.usageLine]
19
- * @param {string} [options.logTitle='ビルドが完了しました']
20
- * @param {boolean} [options.includeOutput=true]
21
- * @param {boolean} [options.log=true]
22
17
  * @returns {Promise<object>}
23
18
  */
24
19
  async function buildProject(projectName, options = {}) {
25
- const workspaceRoot =
26
- options.workspaceRoot || process.cwd();
20
+ const workspaceRoot = options.workspaceRoot || process.cwd();
27
21
  const commandName = options.commandName || 'build';
28
22
 
29
23
  const { config } = loadConfig(workspaceRoot);
@@ -39,7 +33,7 @@ async function buildProject(projectName, options = {}) {
39
33
  }
40
34
 
41
35
  /**
42
- * 解決済みプロジェクトに対して clean → render → copy を実行します。
36
+ * 解決済みプロジェクトに対して clean → pipeline を実行します。
43
37
  *
44
38
  * @param {object} project
45
39
  * @param {object} [options]
@@ -62,10 +56,22 @@ async function runBuild(project, options = {}) {
62
56
  const env = createNunjucksEnv({
63
57
  sourceDir: project.sourceDir,
64
58
  templates: project.templates,
59
+ nunjucks: project.nunjucks,
65
60
  });
66
61
 
67
- const { renderedCount } = await renderPages({ env, project });
68
- const { copiedCount } = await copyFiles({ project });
62
+ let renderedCount = 0;
63
+ let copiedCount = 0;
64
+
65
+ if (project.pipelineMode === 'files') {
66
+ const result = await processFiles({ env, project });
67
+ renderedCount = result.renderedCount;
68
+ copiedCount = result.copiedCount;
69
+ } else {
70
+ const rendered = await renderPages({ env, project });
71
+ const copied = await copyFiles({ project });
72
+ renderedCount = rendered.renderedCount;
73
+ copiedCount = copied.copiedCount;
74
+ }
69
75
 
70
76
  const outputDisplay = path
71
77
  .relative(project.workspaceRoot, project.outputDir)
@@ -92,7 +98,7 @@ function printBuildResult(result, options = {}) {
92
98
 
93
99
  console.log(`[JSKim] ${logTitle}`);
94
100
  console.log(`プロジェクト: ${result.project.name}`);
95
- console.log(`レンダリングしたページ数: ${result.renderedCount}`);
101
+ console.log(`レンダリングしたファイル数: ${result.renderedCount}`);
96
102
  console.log(`コピーしたファイル数: ${result.copiedCount}`);
97
103
  if (includeOutput) {
98
104
  console.log(`出力先: ${result.outputDisplay}`);
@@ -7,13 +7,15 @@ const nunjucks = require('nunjucks');
7
7
  /**
8
8
  * プロジェクト用の Nunjucks 環境を作成します。
9
9
  * loader ルート: sourceDir + templates[](重複除去)。
10
+ * filters / globals を登録します。
10
11
  *
11
12
  * @param {object} options
12
13
  * @param {string} options.sourceDir
13
14
  * @param {string[]} options.templates
15
+ * @param {object} [options.nunjucks]
14
16
  * @returns {nunjucks.Environment}
15
17
  */
16
- function createNunjucksEnv({ sourceDir, templates }) {
18
+ function createNunjucksEnv({ sourceDir, templates, nunjucks: nunjucksConfig }) {
17
19
  const roots = [];
18
20
  const seen = new Set();
19
21
 
@@ -48,14 +50,50 @@ function createNunjucksEnv({ sourceDir, templates }) {
48
50
  noCache: true,
49
51
  });
50
52
 
53
+ // HTML 互換のため autoescape は維持する。
54
+ // JSON/JS 出力が必要な filter は SafeString を返すこと。
51
55
  const env = new nunjucks.Environment(loader, {
52
56
  autoescape: true,
53
57
  noCache: true,
54
58
  });
55
59
 
60
+ const config =
61
+ nunjucksConfig && typeof nunjucksConfig === 'object' ? nunjucksConfig : {};
62
+ registerFilters(env, config.filters || {});
63
+ registerGlobals(env, config.globals || {});
64
+
56
65
  return env;
57
66
  }
58
67
 
68
+ function registerFilters(env, filters) {
69
+ for (const [name, fn] of Object.entries(filters)) {
70
+ env.addFilter(name, wrapSyncCallable(fn, `filter:${name}`));
71
+ }
72
+ }
73
+
74
+ function registerGlobals(env, globals) {
75
+ for (const [name, value] of Object.entries(globals)) {
76
+ if (typeof value === 'function') {
77
+ env.addGlobal(name, wrapSyncCallable(value, `global:${name}`));
78
+ } else {
79
+ env.addGlobal(name, value);
80
+ }
81
+ }
82
+ }
83
+
84
+ function wrapSyncCallable(fn, label) {
85
+ return function wrapped(...args) {
86
+ const result = fn.apply(this, args);
87
+ if (result && typeof result.then === 'function') {
88
+ throw new Error(
89
+ `[JSKim] 非同期${label.startsWith('filter') ? 'filter' : 'global'}は現在サポートされていません。\n` +
90
+ `対象: ${label}`
91
+ );
92
+ }
93
+ return result;
94
+ };
95
+ }
96
+
59
97
  module.exports = {
60
98
  createNunjucksEnv,
61
99
  };