UNPKG

nadesiko3

Version:
257 lines (256 loc) 11.3 kB
// wnako3mod - nadesiko for web browser // - wnako3 から wnako3mod を取り込む。 // - このファイルはモジュールとして別のファイルから取り込み可能。 import { NakoCompiler } from '../core/src/nako3.mjs'; import { NakoImportError } from '../core/src/nako_errors.mjs'; import { setupEditor } from './wnako3_editor.mjs'; import nakoVersion from './nako_version.mjs'; import PluginBrowser from './plugin_browser.mjs'; const NAKO_SCRIPT_RE = /^(なでしこ|nako|nadesiko)3?$/; export class WebNakoCompiler extends NakoCompiler { constructor() { super({ useBasicPlugin: true }); this.version = nakoVersion.version; this.wnakoVersion = nakoVersion; this.localFiles = {}; // プラグインを追加 this.addPluginObject('PluginBrowser', PluginBrowser); // 必要な定数を設定 this.addListener('beforeRun', (g) => { // バージョン情報を設定 g.__varslist[0].set('ナデシコ種類', 'wnako3'); g.__varslist[0].set('ナデシコバージョン', nakoVersion.version); g.__varslist[0].set('WNAKOバージョン', nakoVersion.version); }); } /** * ブラウザでtype="なでしこ"というスクリプトを得て実行する */ async runNakoScript() { // スクリプトタグの中身を得る let nakoScriptCount = 0; const scripts = document.querySelectorAll('script'); for (let i = 0; i < scripts.length; i++) { const script = scripts[i]; if (script.type.match(NAKO_SCRIPT_RE)) { nakoScriptCount++; // URLからスクリプト名を見つける const url = (typeof (window.location) === 'object') ? window.location.href : 'url_unknown'; const fname = `${url}#script${nakoScriptCount}.nako3`; const code = script.text; // 依存するライブラリをロード await this.loadDependencies(code, fname); // プログラムを実行 await this.runAsync(script.text, fname); } } if (nakoScriptCount > 1) { console.log('実行したなでしこの個数=', nakoScriptCount); } } /** 取り込む文を実行する */ async loadDependencies(code, filename, preCode = '', localFiles = {}) { this.localFiles = localFiles || {}; return this._loadDependencies(code, filename, preCode, this.getLoaderTool()); } /** * type=なでしこ のスクリプトを自動実行するべきかどうかを返す * @returns type=なでしこ のスクリプトを自動実行するべきかどうか */ checkScriptTagParam() { const scripts = document.querySelectorAll('script'); for (let i = 0; i < scripts.length; i++) { const script = scripts[i]; const src = script.src || ''; if (src.indexOf('wnako3.js?run') >= 0 || src.indexOf('wnako3.js&run') >= 0) { return true; } } return false; } /** * 指定したidのHTML要素をなでしこ言語のエディタにする。 * @param {string | Element} idOrElement HTML要素 * @see {setupEditor} */ setupEditor(idOrElement) { return setupEditor(idOrElement, this, window.ace); } /** なでしこ3の『取り込む』命令のための読み込みツール */ getLoaderTool() { const tool = { readJs: this.readJs, readNako3: this.readNako3, resolvePath: this.resolvePath }; return tool; } /** JSプラグインの読み込み */ readJs(filePath, token) { if (this.localFiles && this.localFiles[filePath]) { return { task: (async () => () => { // eslint-disable-next-line no-new-func Function(this.localFiles[filePath])(); return {}; })() }; } return { task: (async () => { // もし動的インポートに対応していれば動的インポートを試す const basename = ('/' + filePath).split('/').pop() || '?'; /* try { const obj = await import(filePath) this.addPluginObject(basename, obj) return {} } catch (e) { } */ // WebPackの関係で動的インポートが失敗するので、テキストとしてJSソースを取り出して処理する // JavaScriptをテキストとして取得 const res = await fetch(filePath); if (!res.ok) { throw new NakoImportError(`ファイル『${filePath}』のダウンロードに失敗しました: ${res.status} ${res.statusText}`, token.file, token.line); } let jstext = await res.text(); // ESModuleっぽければ `default export`を置き換える const r = Math.floor(Math.random() * 1000000); const exportName = `exportObject${r}`; if (jstext.includes('default export')) { jstext = jstext.replace('default export', `;let ${exportName} = `); jstext += `\n;navigator.nako3.addPluginObject('${basename}', ${exportName});\n`; } // addPluginObjectが存在する if (!jstext.includes('navigator.nako3.addPluginObject')) { throw new NakoImportError(`ファイル ${filePath} の中に文字列 "navigator.nako3.addPluginObject" が存在しません。現在、ブラウザ版のなでしこ言語v3は自動登録するプラグインのみをサポートしています。`, token.file, token.line); } const gNavigator = navigator; const gSelf = gNavigator.nako3; // textの例: `navigator.nako3.addPluginObject('PluginRequireTest', { requiretest: { type: 'var', value: 100 } })` return () => { // プラグインの自動登録は navigator.nako3 を参照するため、 navigator.nako3 を一時的に現在のインスタンスにする。 const globalNavigator = gNavigator; const globalNako3 = globalNavigator.nako3; globalNavigator.nako3 = gSelf; try { // eslint-disable-next-line no-new-func const f = Function(jstext); f.apply(gSelf.__globalObj); } catch (err) { throw new NakoImportError(`プラグイン ${filePath} の取り込みに失敗: ${err instanceof Error ? err.message : err + ''}`, token.file, token.line); } finally { globalNavigator.nako3 = globalNako3; } return {}; }; })() }; } /** NAKO3プラグインの読み込み */ readNako3(filePath, token) { if (this.localFiles && this.localFiles[filePath]) { // ローカルファイルを使う場合 return { task: (() => { return (new Promise((resolve, reject) => { const s = this.localFiles[filePath]; resolve(s); })); })() }; } return { task: (async () => { const res = await fetch(filePath); if (!res.ok) { throw new NakoImportError(`ファイル ${filePath} のダウンロードに失敗しました: ${res.status} ${res.statusText}`, token.file, token.line); } return await res.text(); })() }; } /** 読み込みでパスを解決する */ resolvePath(name, token, fromFile) { let pathname = name; // http から始まっていれば解決は不要 if (pathname.match(/^https?:\/\//)) { // フルパスなら解決不要 } else if (this.localFiles && this.localFiles[name]) { pathname = this.localFiles[name]; } else { try { pathname = new URL(name).pathname; } catch (e) { // 単純にパスに変換できなければ、location.hrefを参考にパスを組み立てる try { let baseDir = dirname(fromFile); if (baseDir === '') { // https://2/3/4.html const a = window.location.href.split('/'); baseDir = '/' + a.slice(3, a.length - 1).join('/'); } pathname = resolveURL(baseDir, name); } catch (e) { throw new NakoImportError(`取り込み文の引数でパスが解決できません。https:// か http:// で始まるアドレスを指定してください。\n${e}`, token.file, token.line); } } } // 拡張子によってプラグインタイプを分岐する const checkExt = (name, extlist) => { for (const ext of extlist) { if (name.endsWith(ext)) { return true; } if (name.endsWith(ext + '.txt')) { return true; } // '.mjs.txt' にもマッチ } return false; }; // URLパラメータを考慮する const aname = (pathname + '?').split('?')[0]; // JSプラグイン → (.js|.mjs) // NAKO3プラグイン → (.nako3|.nako) if (checkExt(aname, ['.js', '.mjs'])) { return { filePath: pathname, type: 'js' }; } if (checkExt(aname, ['.nako3', '.nako'])) { return { filePath: pathname, type: 'nako3' }; } if (checkExt(pathname, ['.js', '.mjs'])) { return { filePath: pathname, type: 'js' }; } if (checkExt(pathname, ['.nako3', '.nako'])) { return { filePath: pathname, type: 'nako3' }; } // ファイル拡張子が未指定の場合 throw new NakoImportError(`ファイル『${name}』は拡張子が(.nako3|.js|.js.txt|.mjs|.mjs.txt)以外なので取り込めません。`, token.file, token.line); } } function dirname(s) { const a = s.split('/'); if (a && a.length > 1) { return a.slice(0, a.length - 1).join('/'); } return ''; } function resolveURL(base, s) { const baseA = base.split('/'); const sA = s.split('/'); for (const p of sA) { if (p === '') { continue; } if (p === '.') { continue; } if (p === '..') { baseA.pop(); continue; } baseA.push(p); } return baseA.join('/'); }