From b7ab626aa3acb5d869c1e2b87a2e86f611b4709f Mon Sep 17 00:00:00 2001 From: Joel Denning Date: Thu, 19 Dec 2019 12:41:52 -0700 Subject: [PATCH] Implementing generator for single-spa root configs. (#9) * Adding prompts and options for generating root configs and raw in-browser modules * Self review * Self review * Implementing generator for single-spa root configs. * Justin's feedback * Making org name templatized * More fixes * Fixing build * Fixing build * Self review --- .travis.yml | 7 ++ package.json | 7 +- packages/generator-single-spa/package.json | 3 +- .../src/generator-single-spa.js | 16 +++-- .../src/root-config/generator-root-config.js | 69 +++++++++++++++++++ .../src/root-config/templates/.eslintrc | 6 ++ .../src/root-config/templates/package.json | 37 ++++++++++ .../templates/src/activity-functions.js | 4 ++ .../templates/src/activity-functions.test.js | 7 ++ .../src/root-config/templates/src/index.ejs | 30 ++++++++ .../root-config/templates/src/root-config.js | 10 +++ .../root-config/templates/webpack.config.js | 37 ++++++++++ .../test/root-config.test.js | 43 ++++++++++++ packages/generator-single-spa/yarn.lock | 5 ++ yarn.lock | 5 -- 15 files changed, 272 insertions(+), 14 deletions(-) create mode 100644 .travis.yml create mode 100644 packages/generator-single-spa/src/root-config/generator-root-config.js create mode 100644 packages/generator-single-spa/src/root-config/templates/.eslintrc create mode 100644 packages/generator-single-spa/src/root-config/templates/package.json create mode 100644 packages/generator-single-spa/src/root-config/templates/src/activity-functions.js create mode 100644 packages/generator-single-spa/src/root-config/templates/src/activity-functions.test.js create mode 100644 packages/generator-single-spa/src/root-config/templates/src/index.ejs create mode 100644 packages/generator-single-spa/src/root-config/templates/src/root-config.js create mode 100644 packages/generator-single-spa/src/root-config/templates/webpack.config.js create mode 100644 packages/generator-single-spa/test/root-config.test.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..aed2e3c0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - "node" +script: + - yarn bootstrap + - yarn test + - yarn lint \ No newline at end of file diff --git a/package.json b/package.json index 0be88a38..62cca392 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,11 @@ "name": "root", "private": true, "devDependencies": { - "lerna": "^3.15.0", - "yeoman-assert": "^3.1.1" + "lerna": "^3.15.0" }, "scripts": { - "bootstrap": "lerna bootstrap" + "bootstrap": "lerna bootstrap", + "test": "lerna run test", + "lint": "lerna run lint" } } diff --git a/packages/generator-single-spa/package.json b/packages/generator-single-spa/package.json index c110d4be..709a8257 100644 --- a/packages/generator-single-spa/package.json +++ b/packages/generator-single-spa/package.json @@ -8,7 +8,7 @@ "src" ], "scripts": { - "test": "jest" + "test": "jest --testPathIgnorePatterns templates" }, "version": "1.0.0-alpha.0", "main": "src/generator-single-spa.js", @@ -19,6 +19,7 @@ "devDependencies": { "chalk": "^3.0.0", "jest": "^24.9.0", + "yeoman-assert": "^3.1.1", "yeoman-test": "^2.0.0" } } diff --git a/packages/generator-single-spa/src/generator-single-spa.js b/packages/generator-single-spa/src/generator-single-spa.js index 6fd791e2..8031e0cd 100644 --- a/packages/generator-single-spa/src/generator-single-spa.js +++ b/packages/generator-single-spa/src/generator-single-spa.js @@ -1,5 +1,6 @@ const Generator = require('yeoman-generator') const SingleSpaReactGenerator = require('./react/generator-single-spa-react') +const SingleSpaRootConfigGenerator = require('./root-config/generator-root-config') module.exports = class SingleSpaGenerator extends Generator { constructor(args, opts) { @@ -16,8 +17,12 @@ module.exports = class SingleSpaGenerator extends Generator { async composeChildGenerator() { let moduleType = this.options.moduleType + if (!moduleType && this.options.framework) { + moduleType = 'app-parcel' + } + if (!moduleType) { - const answers = await this.prompt([ + moduleType = (await this.prompt([ { type: 'list', name: 'moduleType', @@ -28,13 +33,14 @@ module.exports = class SingleSpaGenerator extends Generator { { name: 'single-spa root config', value: 'root-config' }, ] } - ]) - - moduleType = answers.moduleType + ])).moduleType } if (moduleType === 'root-config') { - throw Error('root config not yet implemented') + this.composeWith({ + Generator: SingleSpaRootConfigGenerator, + path: require.resolve('./root-config/generator-root-config.js'), + }, this.options) } else if (moduleType === 'app-parcel') { await runFrameworkGenerator.call(this) } else if (moduleType === 'raw-module') { diff --git a/packages/generator-single-spa/src/root-config/generator-root-config.js b/packages/generator-single-spa/src/root-config/generator-root-config.js new file mode 100644 index 00000000..05d82b65 --- /dev/null +++ b/packages/generator-single-spa/src/root-config/generator-root-config.js @@ -0,0 +1,69 @@ +const Generator = require('yeoman-generator') +const fs = require('fs').promises +const chalk = require('chalk') + +let times = 0 + +module.exports = class SingleSpaRootConfigGenerator extends Generator { + constructor(args, opts) { + super(args, opts) + + this.option("packageManager", { + type: String + }) + } + async createPackageJson() { + this.packageManager = this.options.packageManager + + if (!this.packageManager) { + this.packageManager = (await this.prompt([ + { + type: "list", + name: "packageManager", + message: "Which package manager do you want to use?", + choices: [ + "yarn", + "npm", + ] + } + ])).packageManager + } + + const templatePackageJson = JSON.parse(await fs.readFile(this.templatePath('package.json'))) + + this.fs.extendJSON( + this.destinationPath("package.json"), + templatePackageJson + ) + } + async copyFiles() { + const templateOptions = await this.prompt([ + { + type: "input", + name: "orgName", + message: "Organization name (use lowercase and dashes)", + }, + ]) + + this.fs.copyTpl( + this.templatePath(), + this.destinationPath(), + templateOptions, + {delimiter: '?'} + ) + } + install() { + this.installDependencies({ + npm: this.packageManager === 'npm', + yarn: this.packageManager === 'yarn', + bower: false, + }) + } + finished() { + this.on(`${this.packageManager}Install:end`, () => { + const coloredFinalInstructions = chalk.bgWhite.black + console.log(coloredFinalInstructions("Project setup complete!")) + console.log(coloredFinalInstructions(`Run '${this.packageManager} start' to boot up your single-spa root config`)) + }) + } +} \ No newline at end of file diff --git a/packages/generator-single-spa/src/root-config/templates/.eslintrc b/packages/generator-single-spa/src/root-config/templates/.eslintrc new file mode 100644 index 00000000..e751f3fe --- /dev/null +++ b/packages/generator-single-spa/src/root-config/templates/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": [ + "important-stuff", + "plugin:prettier/recommended" + ] +} \ No newline at end of file diff --git a/packages/generator-single-spa/src/root-config/templates/package.json b/packages/generator-single-spa/src/root-config/templates/package.json new file mode 100644 index 00000000..8f0b32e2 --- /dev/null +++ b/packages/generator-single-spa/src/root-config/templates/package.json @@ -0,0 +1,37 @@ +{ + "scripts": { + "start": "webpack-dev-server --mode=development --port 9000 --env.local=true", + "lint": "eslint src", + "test": "jest", + "build": "webpack --mode=production" + }, + "husky": { + "hooks": { + "pre-commit": "pretty-quick --staged && eslint src" + } + }, + "devDependencies": { + "@babel/core": "^7.7.4", + "@babel/preset-env": "^7.7.4", + "@types/systemjs": "^6.1.0", + "babel-loader": "^8.0.6", + "clean-webpack-plugin": "^3.0.0", + "eslint": "^6.7.2", + "eslint-config-important-stuff": "^1.1.0", + "eslint-config-prettier": "^6.7.0", + "eslint-plugin-prettier": "^3.1.1", + "html-webpack-plugin": "^3.2.0", + "husky": "^3.1.0", + "jest": "^24.9.0", + "jest-cli": "^24.9.0", + "prettier": "^1.19.1", + "pretty-quick": "^2.0.1", + "serve": "^11.2.0", + "webpack": "^4.41.2", + "webpack-cli": "^3.3.10", + "webpack-dev-server": "^3.9.0" + }, + "dependencies": { + "single-spa": "^4.4.2" + } +} \ No newline at end of file diff --git a/packages/generator-single-spa/src/root-config/templates/src/activity-functions.js b/packages/generator-single-spa/src/root-config/templates/src/activity-functions.js new file mode 100644 index 00000000..91630909 --- /dev/null +++ b/packages/generator-single-spa/src/root-config/templates/src/activity-functions.js @@ -0,0 +1,4 @@ +export function navbar(location) { + // The navbar is always active + return true; +} \ No newline at end of file diff --git a/packages/generator-single-spa/src/root-config/templates/src/activity-functions.test.js b/packages/generator-single-spa/src/root-config/templates/src/activity-functions.test.js new file mode 100644 index 00000000..1aa4b2fa --- /dev/null +++ b/packages/generator-single-spa/src/root-config/templates/src/activity-functions.test.js @@ -0,0 +1,7 @@ +import * as isActive from './activity-functions' + +describe('activity functions', () => { + it('verifies that the navbar is always active', () => { + expect(isActive.navbar(location)).toBe(true) + }) +}) \ No newline at end of file diff --git a/packages/generator-single-spa/src/root-config/templates/src/index.ejs b/packages/generator-single-spa/src/root-config/templates/src/index.ejs new file mode 100644 index 00000000..de1e6102 --- /dev/null +++ b/packages/generator-single-spa/src/root-config/templates/src/index.ejs @@ -0,0 +1,30 @@ + + + + + + + Root Config + + + <% if (isLocal) { %> + + <% } %> + + + + + + + + + + \ No newline at end of file diff --git a/packages/generator-single-spa/src/root-config/templates/src/root-config.js b/packages/generator-single-spa/src/root-config/templates/src/root-config.js new file mode 100644 index 00000000..e0dee490 --- /dev/null +++ b/packages/generator-single-spa/src/root-config/templates/src/root-config.js @@ -0,0 +1,10 @@ +import { registerApplication, start } from "single-spa"; +import * as isActive from "./activity-functions"; + +registerApplication( + "@/navbar", + () => System.import("@/navbar"), + isActive.navbar +); + +start(); \ No newline at end of file diff --git a/packages/generator-single-spa/src/root-config/templates/webpack.config.js b/packages/generator-single-spa/src/root-config/templates/webpack.config.js new file mode 100644 index 00000000..c02da712 --- /dev/null +++ b/packages/generator-single-spa/src/root-config/templates/webpack.config.js @@ -0,0 +1,37 @@ +const path = require("path"); +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); + +module.exports = env => ({ + entry: path.resolve(__dirname, "src/root-config"), + output: { + filename: "root-config.js", + libraryTarget: "system", + path: path.resolve(__dirname, "dist") + }, + devtool: "sourcemap", + module: { + rules: [ + { parser: { system: false } }, + { + test: /\.js$/, + exclude: /node_modules/, + use: [{ loader: "babel-loader" }] + } + ] + }, + devServer: { + historyApiFallback: true + }, + plugins: [ + new HtmlWebpackPlugin({ + inject: false, + template: "src/index.ejs", + templateParameters: { + isLocal: env && env.isLocal + } + }), + new CleanWebpackPlugin() + ], + externals: ["single-spa", /^@\/.+$/] +}); \ No newline at end of file diff --git a/packages/generator-single-spa/test/root-config.test.js b/packages/generator-single-spa/test/root-config.test.js new file mode 100644 index 00000000..580e3d54 --- /dev/null +++ b/packages/generator-single-spa/test/root-config.test.js @@ -0,0 +1,43 @@ +const path = require('path') +const fs = require('fs') +const generator = require('../src/generator-single-spa') +const helpers = require('yeoman-test') +const assert = require('yeoman-assert') + +describe('generator-single-spa', () => { + let runContext + + afterEach(() => { + runContext.cleanTestDirectory() + }) + + it('can run the generator', () => { + runContext = helpers.run(generator) + .withOptions({ + moduleType: "root-config", + }) + .withPrompts({ + packageManager: "yarn", + orgName: 'some-org-name' + }) + + return runContext.then(dir => { + assert.file(path.join(dir, 'package.json')) + + // The webpack config should have their org name in its webpack externals + assert.file(path.join(dir, "webpack.config.js")) + const webpackConfigAsStr = fs.readFileSync(path.join(dir, 'webpack.config.js'), {encoding: "utf-8"}) + expect(webpackConfigAsStr.includes('some-org-name')).toBe(true) + + // The index.ejs file should have their org name in it + assert.file(path.join(dir, "src/index.ejs")) + const indexEjsStr = fs.readFileSync(path.join(dir, 'src/index.ejs'), {encoding: 'utf-8'}) + expect(indexEjsStr.includes('some-org-name')).toBe(true) + + // The root-config.js file should have their org name in it + assert.file(path.join(dir, "src/root-config.js")) + const rootConfigStr = fs.readFileSync(path.join(dir, 'src/root-config.js'), {encoding: 'utf-8'}) + expect(rootConfigStr.includes('some-org-name')).toBe(true) + }) + }) +}) \ No newline at end of file diff --git a/packages/generator-single-spa/yarn.lock b/packages/generator-single-spa/yarn.lock index 40e3f0e5..d76c54b7 100644 --- a/packages/generator-single-spa/yarn.lock +++ b/packages/generator-single-spa/yarn.lock @@ -4432,6 +4432,11 @@ yargs@^13.3.0: y18n "^4.0.0" yargs-parser "^13.1.1" +yeoman-assert@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yeoman-assert/-/yeoman-assert-3.1.1.tgz#9f6fa0ecba7dd007c40f579668cb5dda18c79343" + integrity sha512-bCuLb/j/WzpvrJZCTdJJLFzm7KK8IYQJ3+dF9dYtNs2CUYyezFJDuULiZ2neM4eqjf45GN1KH/MzCTT3i90wUQ== + yeoman-environment@^2.0.5, yeoman-environment@^2.3.0, yeoman-environment@^2.3.4: version "2.6.0" resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.6.0.tgz#db884c778946fec9a41e8980a6b3aa94fe41302d" diff --git a/yarn.lock b/yarn.lock index 10113512..1788b8a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4830,8 +4830,3 @@ yargs@^14.2.2: which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^15.0.0" - -yeoman-assert@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yeoman-assert/-/yeoman-assert-3.1.1.tgz#9f6fa0ecba7dd007c40f579668cb5dda18c79343" - integrity sha512-bCuLb/j/WzpvrJZCTdJJLFzm7KK8IYQJ3+dF9dYtNs2CUYyezFJDuULiZ2neM4eqjf45GN1KH/MzCTT3i90wUQ==