UNPKG

@knapsack/app

Version:

Build Design Systems on top of knapsack, by Basalt

432 lines (361 loc) • 15 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.KnapsackRendererWebpackBase = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _fsExtra = _interopRequireDefault(require("fs-extra")); var _path = _interopRequireDefault(require("path")); var _webpack = require("webpack"); var _webpackManifestPlugin = _interopRequireDefault(require("webpack-manifest-plugin")); var _changeCase = require("change-case"); var _webpackVirtualModules = _interopRequireDefault(require("webpack-virtual-modules")); var _ejs = require("ejs"); var _events = require("./events"); var log = _interopRequireWildcard(require("../cli/log")); var _rendererBase = require("./renderer-base"); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } // should root be `dataDir` or CWD? const entryPath = _path.default.join(process.cwd(), 'ks-entry.js'); const ksBootstrapEntryPath = _path.default.join(process.cwd(), 'ks-boostrap.js'); function upperCamelCase(str) { const cased = (0, _changeCase.camelCase)(str); return cased.charAt(0).toUpperCase() + cased.slice(1); } const renderEntryTemplate = (0, _ejs.compile)(_fsExtra.default.readFileSync(_path.default.join(__dirname, './templates/renderer-webpack-base-entry.ejs'), 'utf-8'), { filename: 'renderer-webpack-base-entry.ejs', async: false }); function getEntryString({ entryData: { patterns, extras = [] }, format }) { let entryString = renderEntryTemplate({ patterns, extras }); if (format) { entryString = _rendererBase.KnapsackRendererBase.formatCode({ code: entryString, language: 'ts' }); } return entryString; } class KnapsackRendererWebpackBase extends _rendererBase.KnapsackRendererBase { constructor({ id, extension, language, webpackConfig, webpack, extraScripts = [] }) { super({ id, extension, language }); (0, _defineProperty2.default)(this, "webpack", void 0); (0, _defineProperty2.default)(this, "webpackConfig", void 0); (0, _defineProperty2.default)(this, "entryData", void 0); (0, _defineProperty2.default)(this, "publicPath", void 0); (0, _defineProperty2.default)(this, "language", void 0); (0, _defineProperty2.default)(this, "restartWebpackWatch", void 0); (0, _defineProperty2.default)(this, "webpackCompiler", void 0); (0, _defineProperty2.default)(this, "entriesManifest", void 0); (0, _defineProperty2.default)(this, "webpackWatcher", void 0); (0, _defineProperty2.default)(this, "patterns", void 0); (0, _defineProperty2.default)(this, "virtualModules", void 0); (0, _defineProperty2.default)(this, "extraScripts", void 0); (0, _defineProperty2.default)(this, "webpackEntryPathsManifest", void 0); this.webpack = webpack; this.webpackConfig = webpackConfig; this.extraScripts = extraScripts; } createWebpackCompiler(entryData) { const { plugins = [] } = this.webpackConfig; const { patterns, extras } = entryData; const entryString = getEntryString({ entryData: { patterns, extras }, format: true }); // for debug, upcomment: // fs.writeFileSync(path.join(process.cwd(), 'ks-entry--fyi.js'), entryString); const virtualWebpackEntries = { [entryPath]: entryString, [ksBootstrapEntryPath]: ` import knapsack from '${entryPath}'; //console.log('Multi Entry Knapsack!', { knapsack }); window.knapsack = knapsack; // create and dispatch the event const ksReadyEvent = new CustomEvent('KsRendererClientManifestReady', { detail: knapsack, }); document.dispatchEvent(ksReadyEvent); ` }; this.virtualModules = new _webpackVirtualModules.default(virtualWebpackEntries); const newWebpackConfig = _objectSpread({ optimization: { minimize: process.env.NODE_ENV === 'production', runtimeChunk: 'single', splitChunks: { name: true, chunks: 'all', maxInitialRequests: 8, maxAsyncRequests: 20, maxSize: 300000 } } }, this.webpackConfig, { entry: { main: [...this.extraScripts, ...Object.keys(virtualWebpackEntries)] }, mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', externals: { react: 'React', 'react-dom': 'ReactDOM' }, output: { filename: '[name].bundle.[hash].js', path: this.outputDir, publicPath: this.publicPath, chunkFilename: '[name].chunk.[hash].js' }, plugins: [...plugins, this.virtualModules, new _webpackManifestPlugin.default({ writeToFileEmit: true, generate: (seed, files, entrypoints) => { // Tapping into this so we can get the actual entrypoints: if `entry.main` is the key, then a `string[]` of all the JS/CSS needed for it is desired. The original `manifest.json` made didn't work as it contained a single `string` & only described the output, not all the CSS/JS needed to make that entrypoint work. Originally, we had an entrypoint per React component to render, but now we have a single entrypoint that has a bunch of async functions to fetch any React Component needed. const data = { entrypoints: {} }; Object.keys(entrypoints).forEach(id => { const assets = entrypoints[id]; data.entrypoints[id] = assets.map(asset => encodeURI(_path.default.join(this.publicPath, asset))); }); _fsExtra.default.writeFileSync(this.webpackEntryPathsManifest, JSON.stringify(data)); // the original default "generate the manfiest" function return files.reduce((manifest, { name, path: filePath }) => _objectSpread({}, manifest, { [name]: filePath }), seed); } })] }); this.webpackCompiler = this.webpack(newWebpackConfig); log.verbose('New Webpack Config and Compiler created', null, this.logPrefix); } createWebpackEntryDataFromPatterns(patterns) { const entryData = { patterns: [], extras: [] }; patterns.getPatterns().forEach(pattern => { const patternTemplates = []; pattern.templates.filter(t => t.templateLanguageId === this.id).forEach(template => { var _ref; const templateDemos = []; const absPath = patterns.getTemplateAbsolutePath({ patternId: pattern.id, templateId: template.id }); const demos = Object.values((_ref = template === null || template === void 0 ? void 0 : template.demosById) !== null && _ref !== void 0 ? _ref : {}); if (demos) { demos.filter(KnapsackRendererWebpackBase.isTemplateDemo).forEach(demo => { var _demo$templateInfo; if (demo === null || demo === void 0 ? void 0 : (_demo$templateInfo = demo.templateInfo) === null || _demo$templateInfo === void 0 ? void 0 : _demo$templateInfo.path) { const demoAbsPath = patterns.getTemplateDemoAbsolutePath({ patternId: pattern.id, templateId: template.id, demoId: demo.id }); const entryItem = { id: demo.id, path: demoAbsPath, alias: demo.templateInfo.alias, name: this.getReactName({ pattern, template, demo }) }; templateDemos.push(entryItem); } }); } const entryItem = { id: template.id, path: absPath, alias: template.alias, name: this.getReactName({ pattern, template }) }; patternTemplates.push(_objectSpread({}, entryItem, { demos: templateDemos })); }); entryData.patterns.push({ id: pattern.id, templates: patternTemplates }); }); return { patterns: entryData.patterns, extras: entryData.extras }; } getReactName({ pattern, template, demo }) { var _pattern$templates$fi; const pId = pattern.id; const tId = template.id; if (demo) { if (!KnapsackRendererWebpackBase.isTemplateDemo(demo)) { log.inspect(demo, 'demo'); throw new Error(`Can't run getReactName on non-template demos`); } const { alias } = demo.templateInfo; const isNamedImport = alias && alias !== 'default'; return upperCamelCase(`${pId} ${tId} ${isNamedImport ? alias : ''} Demo ${demo.id}`); } const { alias, templateLanguageId } = template; const isNamedImport = alias && alias !== 'default'; const isOnlyLanguage = ((_pattern$templates$fi = pattern.templates.filter(t => t.templateLanguageId === templateLanguageId)) === null || _pattern$templates$fi === void 0 ? void 0 : _pattern$templates$fi.length) === 1; if (isNamedImport) { var _pattern$templates$fi2; const isOnlyWithThisNamedImport = ((_pattern$templates$fi2 = pattern.templates.filter(t => t.alias === alias)) === null || _pattern$templates$fi2 === void 0 ? void 0 : _pattern$templates$fi2.length) === 1; return isOnlyWithThisNamedImport ? alias : upperCamelCase(`${alias} ${tId}`); } return upperCamelCase(isOnlyLanguage ? pId : `${pId} ${tId}`); } async init(opt) { await super.init(opt); this.publicPath = `/${_path.default.relative(this.cacheDir, this.outputDir)}/`; this.patterns = opt.patterns; this.webpackEntryPathsManifest = _path.default.join(this.outputDir, 'manifest--entries.json'); } setManifest() { return _fsExtra.default.readFile(this.webpackEntryPathsManifest, 'utf8').then(manifestString => JSON.parse(manifestString)).then(manifest => { this.entriesManifest = manifest; }).catch(error => { log.error('setManifest()', error); throw new Error(`Error getting WebPack manifest--entries.json file. ${error.message}`); }); } setManifestSync() { try { const manifestString = _fsExtra.default.readFileSync(this.webpackEntryPathsManifest, 'utf8'); const manifest = JSON.parse(manifestString); this.entriesManifest = manifest; } catch (error) { log.error('setManifest()', error); throw new Error(`Error getting WebPack manifest--entries.json file. ${error.message}`); } } getWebPackEntryPath(id) { var _this$entriesManifest; if (!this.entriesManifest) this.setManifestSync(); if (!this.entriesManifest) { throw new Error(`Webpack has not been built yet, cannot access id "${id}"`); } const result = (_this$entriesManifest = this.entriesManifest) === null || _this$entriesManifest === void 0 ? void 0 : _this$entriesManifest.entrypoints[id]; if (!result) { var _ref2, _this$entriesManifest2; const msg = `Could not find webpack entry "${id}".`; console.error(`Possible ids: "${Object.keys((_ref2 = (_this$entriesManifest2 = this.entriesManifest) === null || _this$entriesManifest2 === void 0 ? void 0 : _this$entriesManifest2.entrypoints) !== null && _ref2 !== void 0 ? _ref2 : {})}"`); throw new Error(msg); } return result; } build() { return new Promise((resolve, reject) => { this.entryData = this.createWebpackEntryDataFromPatterns(this.patterns); this.createWebpackCompiler(this.entryData); this.webpackCompiler.run(async (err, stats) => { if (err || stats.hasErrors()) { log.error(stats.toString(), err, this.logPrefix); reject(); return; } await this.setManifest(); resolve(); }); }); } webpackWatch() { log.verbose('Starting Webpack watch...', null, this.logPrefix); const watchOptions = {}; return this.webpackCompiler.watch(watchOptions, async (err, stats) => { if (err || stats.hasErrors()) { log.error(stats.toString(), err, this.logPrefix); return; } await this.setManifest(); log.info('Webpack recompiled', null, this.logPrefix); super.onChange({ path: '' }); // @todo get path of file changed from `stats` and pass it in here }); } async watch({ templatePaths }) { await super.watch({ templatePaths }); this.entryData = this.createWebpackEntryDataFromPatterns(this.patterns); this.createWebpackCompiler(this.entryData); _events.knapsackEvents.on(_events.EVENTS.PATTERNS_DATA_READY, allPatterns => { const entryData = this.createWebpackEntryDataFromPatterns(this.patterns); if (JSON.stringify(this.entryData) !== JSON.stringify(entryData)) { // @todo enure the new data from `entryData` does trigger the proper re-render w/o restarting WebPack. This event is usually fired when a new pattern template or template demo is added this.entryData = entryData; const entryString = getEntryString({ entryData: this.entryData, format: true }); this.virtualModules.writeModule(entryPath, entryString); // Old "restart WebPack watcher" code below: // this.createWebpackCompiler(entryData); // if (this.restartWebpackWatch) { // this.restartWebpackWatch(); // } } }); this.restartWebpackWatch = () => { log.verbose('Restarting Webpack Watch', null, this.logPrefix); this.webpackWatcher.close(() => { log.verbose('Restarted Webpack Watch', null, this.logPrefix); this.webpackWatcher = this.webpackWatch(); }); }; this.webpackWatcher = this.webpackWatch(); } // eslint-disable-next-line class-methods-use-this onChange() {// overwriting so we can call event after webpack compiles } } exports.KnapsackRendererWebpackBase = KnapsackRendererWebpackBase;