@knapsack/app
Version:
Build Design Systems on top of knapsack, by Basalt
432 lines (361 loc) • 15 kB
JavaScript
;
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;