From 36ae9a03b520b94814cb5a44c03abf8a30f0244f Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 17 Apr 2019 07:26:32 -0400 Subject: [PATCH 01/11] refactored config and compilation, build task is working --- packages/cli/config/webpack.config.common.js | 183 +++++++++--------- packages/cli/config/webpack.config.develop.js | 114 ++++++----- packages/cli/config/webpack.config.prod.js | 81 ++++---- packages/cli/index.js | 10 +- packages/cli/lib/compile.js | 37 ++++ packages/cli/lib/generate.js | 45 ----- packages/cli/lib/graph.js | 8 +- packages/cli/lib/init.js | 46 +++-- packages/cli/lib/scaffold.js | 53 +++-- packages/cli/lib/serialize.js | 13 +- packages/cli/tasks/build.js | 23 +-- packages/cli/tasks/develop.js | 4 +- 12 files changed, 320 insertions(+), 297 deletions(-) create mode 100644 packages/cli/lib/compile.js delete mode 100644 packages/cli/lib/generate.js diff --git a/packages/cli/config/webpack.config.common.js b/packages/cli/config/webpack.config.common.js index 4a48ba3c2..af6ac8282 100644 --- a/packages/cli/config/webpack.config.common.js +++ b/packages/cli/config/webpack.config.common.js @@ -1,6 +1,6 @@ +const fs = require('fs'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); -const fs = require('fs'); const webpack = require('webpack'); const isDirectory = source => fs.lstatSync(source).isDirectory(); @@ -8,104 +8,103 @@ const getUserWorkspaceDirectories = (source) => { return fs.readdirSync(source).map(name => path.join(source, name)).filter(isDirectory); }; -// TODO get userWorkspace and pagesDir from greenwood config? -// https://github.com/ProjectEvergreen/greenwood/issues/11 -const userWorkspace = fs.existsSync(path.join(process.cwd(), 'src')) - ? path.join(process.cwd(), 'src') - : path.join(__dirname, '..', 'templates/'); - -const pagesDir = fs.existsSync(path.join(process.cwd(), 'src', 'pages')) - ? path.join(process.cwd(), 'src', 'pages/') - : path.join(__dirname, '..', 'templates/'); +module.exports = (context) => { + // dynamically map all the user's workspace directories for resolution by webpack + // this essentially helps us keep watch over changes from the user, and greenwood's build pipeline + // TODO make a function ? + const mappedUserDirectoriesForWebpack = getUserWorkspaceDirectories(context.userWorkspace).map((userPath) => { + const directory = userPath.split('/')[userPath.split('/').length - 1]; -const mappedUserDirectoriesForWebpack = getUserWorkspaceDirectories(userWorkspace).map((userPath) => { - const directory = userPath.split('/')[userPath.split('/').length - 1]; - - return new webpack.NormalModuleReplacementPlugin( - new RegExp(`${directory}`), - (resource) => { - resource.request = resource.request.replace(new RegExp(`\.\.\/${directory}`), userPath); - - // remove any additional nests, after replacement with absolute path of user workspace + directory - const additionalNestedPathIndex = resource.request.lastIndexOf('..'); - - if (additionalNestedPathIndex > -1) { - resource.request = resource.request.substring(additionalNestedPathIndex + 2, resource.request.length); + return new webpack.NormalModuleReplacementPlugin( + new RegExp(`${directory}`), + (resource) => { + resource.request = resource.request.replace(new RegExp(`\.\.\/${directory}`), userPath); + + // remove any additional nests, after replacement with absolute path of user workspace + directory + const additionalNestedPathIndex = resource.request.lastIndexOf('..'); + + if (additionalNestedPathIndex > -1) { + resource.request = resource.request.substring(additionalNestedPathIndex + 2, resource.request.length); + } } - }); -}); + ); + }); -module.exports = { + return { - entry: { - index: path.join(process.cwd(), '.greenwood', 'app', 'app.js') - }, + entry: { + // TODO magic string - greenwood, app, app.js + index: path.join(process.cwd(), '.greenwood', 'app', 'app.js') + }, - output: { - path: path.join(process.cwd(), 'public'), - filename: '[name].[hash].bundle.js', - publicPath: '/' - }, + output: { + // TODO magic string - public + path: path.join(process.cwd(), 'public'), + filename: '[name].[hash].bundle.js', + publicPath: '/' + }, - module: { - rules: [{ - test: /\.js$/, - enforce: 'pre', - loader: 'eslint-loader', - options: { - configFile: path.join(__dirname, './.eslintrc') - } - }, { - test: /\.js$/, - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'], - plugins: [ - ['babel-plugin-transform-builtin-classes', { - globals: ['LitElement'] - }] - ] - } - }, { - test: /\.md$/, - loaders: [ - 'babel-loader', - 'wc-markdown-loader' - ] - }, { - test: /\.css$/, - loaders: [ - { loader: 'css-to-string-loader' }, - { loader: 'css-loader' }, - { loader: 'postcss-loader', options: - { - config: { - path: path.join(__dirname) - } - } + module: { + rules: [{ + test: /\.js$/, + enforce: 'pre', + loader: 'eslint-loader', + options: { + configFile: path.join(__dirname, './.eslintrc') } - ] - }, { - test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, { - test: /\.(ttf|eot|svg|jpe?g|png|gif|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/, - loader: 'file-loader' - }] - }, + }, { + test: /\.js$/, + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'], + plugins: [ + ['babel-plugin-transform-builtin-classes', { + globals: ['LitElement'] + }] + ] + } + }, { + test: /\.md$/, + loaders: [ + 'babel-loader', + 'wc-markdown-loader' + ] + }, { + test: /\.css$/, + loaders: [ + { loader: 'css-to-string-loader' }, + { loader: 'css-loader' }, + { loader: 'postcss-loader', options: + { + config: { + path: path.join(__dirname) + } + } + } + ] + }, { + test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'url-loader?limit=10000&mimetype=application/font-woff' + }, { + test: /\.(ttf|eot|svg|jpe?g|png|gif|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'file-loader' + }] + }, - plugins: [ - ...mappedUserDirectoriesForWebpack, + plugins: [ + ...mappedUserDirectoriesForWebpack, - new webpack.NormalModuleReplacementPlugin( - /\.md/, - (resource) => { - resource.request = resource.request.replace(/^\.\//, pagesDir); - }), - - new HtmlWebpackPlugin({ - template: path.join(process.cwd(), '.greenwood', 'index.html'), - chunksSortMode: 'dependency' - }) - ] + new webpack.NormalModuleReplacementPlugin( + /\.md/, + (resource) => { + resource.request = resource.request.replace(/^\.\//, context.pagesDir); + } + ), + + new HtmlWebpackPlugin({ + template: path.join(process.cwd(), '.greenwood', 'index.html'), + chunksSortMode: 'dependency' + }) + ] + }; }; \ No newline at end of file diff --git a/packages/cli/config/webpack.config.develop.js b/packages/cli/config/webpack.config.develop.js index 8c1987b85..04c1b54b3 100644 --- a/packages/cli/config/webpack.config.develop.js +++ b/packages/cli/config/webpack.config.develop.js @@ -1,29 +1,22 @@ -const fs = require('fs'); +// const fs = require('fs'); const path = require('path'); -const commonConfig = require('./webpack.config.common'); -const webpackMerge = require('webpack-merge'); +// const commonConfig = require('./webpack.config.common'); +// const webpackMerge = require('webpack-merge'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); const FilewatcherPlugin = require('filewatcher-webpack-plugin'); -const generateBuild = require('../lib/generate'); +const generateCompilation = require('../lib/compile'); const host = 'localhost'; const port = 1981; -const publicPath = commonConfig.publicPath; +const publicPath = '/'; // commonConfig.publicPath; let isRebuilding = false; -// TODO get userWorkspace and pagesDir from greenwood config? -// https://github.com/ProjectEvergreen/greenwood/issues/11 - -const userWorkspace = fs.existsSync(path.join(process.cwd(), 'src')) - ? path.join(process.cwd(), 'src') - : path.join(__dirname, '..', 'templates/'); - const rebuild = async() => { if (!isRebuilding) { isRebuilding = true; // rebuild web components - await generateBuild(); + await generateCompilation(); // debounce setTimeout(() => { isRebuilding = false; @@ -31,51 +24,56 @@ const rebuild = async() => { } }; -module.exports = webpackMerge(commonConfig, { - - mode: 'development', +module.exports = getDevelopConfig = (context) => { + + return { + mode: 'development', - entry: [ - `webpack-dev-server/client?http://${host}:${port}`, - path.join(process.cwd(), '.greenwood', 'app', 'app.js') - ], + // TODO magic strings - .greenwood, app, app.js + entry: [ + `webpack-dev-server/client?http://${host}:${port}`, + path.join(process.cwd(), '.greenwood', 'app', 'app.js') + ], - devServer: { - port, - host, - historyApiFallback: true, - hot: false, - inline: true - }, + devServer: { + port, + host, + historyApiFallback: true, + hot: false, + inline: true + }, - plugins: [ - // new webpack.HotModuleReplacementPlugin(), - new FilewatcherPlugin({ - watchFileRegex: [`/${userWorkspace}/`], - onReadyCallback: () => { - console.log(`Now serving Development Server available at http://${host}:${port}`); - }, - // eslint-disable-next-line no-unused-vars - onChangeCallback: async (path) => { - rebuild(); - }, - usePolling: true, - atomic: true, - ignored: '/node_modules/' - }), - new ManifestPlugin({ - fileName: 'manifest.json', - publicPath - }), - new HtmlWebpackPlugin({ - filename: 'index.html', - template: '.greenwood/index.dev.html', - publicPath - }), - new HtmlWebpackPlugin({ - filename: '404.html', - template: '.greenwood/404.dev.html', - publicPath - }) - ] -}); \ No newline at end of file + plugins: [ + // new webpack.HotModuleReplacementPlugin(), + new FilewatcherPlugin({ + watchFileRegex: [`/${context.userWorkspace}/`], + onReadyCallback: () => { + console.log(`Now serving Development Server available at http://${host}:${port}`); + }, + // eslint-disable-next-line no-unused-vars + onChangeCallback: async () => { + rebuild(); + }, + usePolling: true, + atomic: true, + ignored: '/node_modules/' + }), + new ManifestPlugin({ + fileName: 'manifest.json', + publicPath + }), + // TODO magic string paths (index.html) + new HtmlWebpackPlugin({ + filename: 'index.html', + template: '.greenwood/index.dev.html', + publicPath + }), + // TODO magic string paths (404.html) + new HtmlWebpackPlugin({ + filename: '404.html', + template: '.greenwood/404.dev.html', + publicPath + }) + ] + }; +}; \ No newline at end of file diff --git a/packages/cli/config/webpack.config.prod.js b/packages/cli/config/webpack.config.prod.js index 2aa16a6e6..68b29d4d2 100644 --- a/packages/cli/config/webpack.config.prod.js +++ b/packages/cli/config/webpack.config.prod.js @@ -1,49 +1,50 @@ -const commonConfig = require('./webpack.config.common'); // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; // const FaviconsWebpackPlugin = require('favicons-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); -const webpackMerge = require('webpack-merge'); +module.exports = () => { -module.exports = webpackMerge(commonConfig, { + return { - mode: 'production', + mode: 'production', - performance: { - hints: 'error' - }, + performance: { + hints: 'error' + }, - plugins: [ - new HtmlWebpackPlugin({ - filename: '404.html', - template: '.greenwood/404.html', - publicPath: commonConfig.publicPath - }) - // new FaviconsWebpackPlugin({ - // logo: './favicon.png', - // emitStats: true, - // prefix: 'icons/', - // statsFilename: 'icons/stats.json', - // inject: true, - // title: 'Create Evergreen App', - // background: '#466628', - // icons: { - // android: true, - // appleIcon: false, - // appleStartup: false, - // coast: false, - // favicons: true, - // firefox: true, - // opengraph: true, - // twitter: true, - // yandex: false, - // windows: false - // } - // }), + plugins: [ + // TODO magic strings 404 html + new HtmlWebpackPlugin({ + filename: '404.html', + template: '.greenwood/404.html', + publicPath: '/' // TOOD reuse from common config + }) + // new FaviconsWebpackPlugin({ + // logo: './favicon.png', + // emitStats: true, + // prefix: 'icons/', + // statsFilename: 'icons/stats.json', + // inject: true, + // title: 'Create Evergreen App', + // background: '#466628', + // icons: { + // android: true, + // appleIcon: false, + // appleStartup: false, + // coast: false, + // favicons: true, + // firefox: true, + // opengraph: true, + // twitter: true, + // yandex: false, + // windows: false + // } + // }), - // new BundleAnalyzerPlugin({ - // analyzerMode: 'static', - // openAnalyzer: false - // }) - ] -}); \ No newline at end of file + // new BundleAnalyzerPlugin({ + // analyzerMode: 'static', + // openAnalyzer: false + // }) + ] + }; +}; \ No newline at end of file diff --git a/packages/cli/index.js b/packages/cli/index.js index 0bfe46e69..07c586d36 100644 --- a/packages/cli/index.js +++ b/packages/cli/index.js @@ -3,6 +3,7 @@ require('colors'); const chalk = require('chalk'); const path = require('path'); const program = require('commander'); +const generateCompilation = require('./lib/compile'); const runProdBuild = require('./tasks/build'); const runDevServer = require('./tasks/develop'); const scriptPkg = require(path.join(__dirname, '../..', 'package.json')); @@ -50,12 +51,17 @@ if (program.parse.length === 0) { } const run = async() => { + // 1) init context + // 2) generate graph + // 3) scaffolding + // TODO distinguish compilation from scaffolding? + const compilation = await generateCompilation(); try { switch (MODE) { case 'build': - await runProdBuild(); + await runProdBuild(compilation); console.log('...................................'.yellow); console.log('Static site generation complete!'); console.log('Serve with: '.cyan + 'greenwood serve'.green); @@ -63,7 +69,7 @@ const run = async() => { break; case 'develop': console.log('Development Mode Activated'); - await runDevServer(); + await runDevServer(compilation); break; case 'create': console.log('Creating Greenwood application...'); diff --git a/packages/cli/lib/compile.js b/packages/cli/lib/compile.js new file mode 100644 index 000000000..1226bc2f2 --- /dev/null +++ b/packages/cli/lib/compile.js @@ -0,0 +1,37 @@ +require('colors'); +const initContext = require('./init'); +const generateGraph = require('./graph'); +const generateScaffolding = require('./scaffold'); + +// TODO would like to move graph and scaffold to the top more maybe? +module.exports = generateCompilation = () => { + return new Promise(async (resolve, reject) => { + try { + + let compilation = { + graph: [], + context: {} + }; + + // determine whether to use default template or user detected workspace + console.log('Initializing project workspace contexts'); + const context = await initContext(compilation); + + compilation.context = context; + + // generate a graph of all pages / components to build + console.log('Generating graph of workspace files...'); + const graph = await generateGraph(compilation); + + compilation.graph = graph; + + // generate scaffolding + console.log('Scaffolding out project files...'); + await generateScaffolding(compilation); + + resolve(compilation); + } catch (err) { + reject(err); + } + }); +}; \ No newline at end of file diff --git a/packages/cli/lib/generate.js b/packages/cli/lib/generate.js deleted file mode 100644 index 1aa914dd5..000000000 --- a/packages/cli/lib/generate.js +++ /dev/null @@ -1,45 +0,0 @@ -require('colors'); -const path = require('path'); -const initDirectories = require('./init'); -const generateGraph = require('./graph'); -const generateScaffolding = require('./scaffold'); - -let config = { - pagesDir: path.join(__dirname, '../templates'), - scratchDir: path.join(process.cwd(), './.greenwood/'), - templatesDir: path.join(__dirname, '../templates/'), - publicDir: path.join(process.cwd(), './public'), - pageTemplate: 'page-template.js', - appTemplate: 'app-template.js', - rootComponent: path.join(__dirname, '../templates', 'index.js'), - defaultTemplates: path.join(__dirname, '../templates/'), // static - default: true -}; - -module.exports = generateBuild = () => { - return new Promise(async (resolve, reject) => { - try { - - let compilation = { - graph: [] - }; - - // determine whether to use default template or user directories - console.log('Checking src directory'); - config = await initDirectories(config); - - // generate a graph of all pages / components to build - console.log('Generating graph of project files...'); - let graph = await generateGraph(config, compilation); - - compilation.graph = compilation.graph.concat(graph); - - // generate scaffolding - console.log('Scaffolding out application files...'); - await generateScaffolding(config, compilation); - resolve({ config, compilation }); - } catch (err) { - reject(err); - } - }); -}; \ No newline at end of file diff --git a/packages/cli/lib/graph.js b/packages/cli/lib/graph.js index fa2cdb823..2979cc510 100644 --- a/packages/cli/lib/graph.js +++ b/packages/cli/lib/graph.js @@ -55,10 +55,10 @@ const createGraphFromPages = async (pagesDir) => { // set route to the nested pages path and file name(without extension) route = completeNestedPath + route; - mdFile = `.${completeNestedPath}${fileRoute}.md`; + mdFile = `./${completeNestedPath}${fileRoute}.md`; relativeExpectedPath = `'..${completeNestedPath}/${fileName}/${fileName}.js'`; } else { - mdFile = `.${fileRoute}.md`; + mdFile = `./${fileRoute}.md`; relativeExpectedPath = `'../${fileName}/${fileName}.js'`; } @@ -97,11 +97,11 @@ const createGraphFromPages = async (pagesDir) => { }); }; -module.exports = generateGraph = async (config) => { +module.exports = generateGraph = async (compilation) => { return new Promise(async (resolve, reject) => { try { - const graph = await createGraphFromPages(config.pagesDir); + const graph = await createGraphFromPages(compilation.context.pagesDir); resolve(graph); } catch (err) { diff --git a/packages/cli/lib/init.js b/packages/cli/lib/init.js index 68ceba502..e84847afb 100644 --- a/packages/cli/lib/init.js +++ b/packages/cli/lib/init.js @@ -1,36 +1,52 @@ const fs = require('fs'); const path = require('path'); -module.exports = initDirectories = async(config) => { +const userWorkspace = fs.existsSync(path.join(process.cwd(), 'src')) + ? path.join(process.cwd(), 'src') + : path.join(__dirname, '..', 'templates/'); + +const pagesDir = fs.existsSync(path.join(process.cwd(), 'src', 'pages')) + ? path.join(process.cwd(), 'src', 'pages/') + : path.join(__dirname, '..', 'templates/'); + +const templatesDir = fs.existsSync(path.join(process.cwd(), 'src', 'templates')) + ? path.join(process.cwd(), 'src', 'templates/') + : path.join(__dirname, '..', 'templates/'); + +module.exports = initContexts = async() => { return new Promise((resolve, reject) => { try { - const usrPagesDir = path.join(process.cwd(), './src/pages'); - const usrTemplateDir = path.join(process.cwd(), './src/templates'); + const context = { + userWorkspace, + pagesDir, + scratchDir: path.join(process.cwd(), './.greenwood/'), + templatesDir, + publicDir: path.join(process.cwd(), './public'), + pageTemplate: 'page-template.js', + appTemplate: 'app-template.js' + // default: true + }; - if (fs.existsSync(usrPagesDir)) { - config.pagesDir = usrPagesDir; - } - if (fs.existsSync(usrTemplateDir)) { - if (!fs.existsSync(path.join(usrTemplateDir, config.pageTemplate))) { + if (fs.existsSync(templatesDir)) { + if (!fs.existsSync(path.join(templatesDir, context.pageTemplate))) { reject('It looks like you don\'t have a page template defined. \n' + 'Please include a page-template.js in your templates directory. \n' + 'See https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/templates/page-template.js'); } - if (!fs.existsSync(path.join(usrTemplateDir, config.appTemplate))) { + if (!fs.existsSync(path.join(templatesDir, context.appTemplate))) { reject('It looks like you don\'t have an app template defined. \n' + 'Please include an app-template.js in your templates directory. \n' + 'See https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/templates/app-template.js'); } - /// set templates directory to user's src/templates directory - config.templatesDir = usrTemplateDir; + /// set default flag that we're using a user template - config.default = false; + // config.default = false; } - if (!fs.existsSync(config.scratchDir)) { - fs.mkdirSync(config.scratchDir); + if (!fs.existsSync(context.scratchDir)) { + fs.mkdirSync(context.scratchDir); } - resolve(config); + resolve(context); } catch (err) { reject(err); } diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 6dbc906d7..3a65b9cfe 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -1,11 +1,11 @@ const fs = require('fs'); const path = require('path'); -const writePageComponentsFromTemplate = async (config, compilation) => { +const writePageComponentsFromTemplate = async (compilation) => { const createPageComponent = async (file) => { return new Promise(async (resolve, reject) => { try { - let data = await fs.readFileSync(path.join(config.templatesDir, `${file.template}-template.js`)); + let data = await fs.readFileSync(path.join(compilation.context.templatesDir, `${file.template}-template.js`)); let result = data.toString().replace(/entry/g, `wc-md-${file.label}`); result = result.replace(/page-template/g, `eve-${file.label}`); @@ -19,17 +19,19 @@ const writePageComponentsFromTemplate = async (config, compilation) => { }; return Promise.all(compilation.graph.map(file => { + const context = compilation.context; + return new Promise(async(resolve, reject) => { try { let result = await createPageComponent(file); - let relPageDir = file.filePath.substring(config.pagesDir.length, file.filePath.length); + let relPageDir = file.filePath.substring(context.pagesDir.length, file.filePath.length); const pathLastBackslash = relPageDir.lastIndexOf('/'); - target = path.join(config.scratchDir, file.fileName); // non-nested default + target = path.join(context.scratchDir, file.fileName); // non-nested default if (pathLastBackslash !== 0) { - target = path.join(config.scratchDir, relPageDir.substring(0, pathLastBackslash), file.fileName); // nested path + target = path.join(context.scratchDir, relPageDir.substring(0, pathLastBackslash), file.fileName); // nested path } if (!fs.existsSync(target)) { @@ -46,25 +48,27 @@ const writePageComponentsFromTemplate = async (config, compilation) => { }; -const writeListImportFile = async (config, compilation) => { +const writeListImportFile = async (compilation) => { let arr = compilation.graph.map(file => { return `import ${file.relativeExpectedPath};\n`; }); - /// Create app directory so that app-template relative imports are correct - const appDir = path.join(config.scratchDir, 'app'); + // Create app directory so that app-template relative imports are correct + // TODO magic string - app + const appDir = path.join(compilation.context.scratchDir, 'app'); if (!fs.existsSync(appDir)) { await fs.mkdirSync(appDir); } + // TODO magic string - list.js return await fs.writeFileSync(path.join(appDir, './list.js'), arr.join('')); }; -const writeRoutes = async(config, compilation) => { +const writeRoutes = async(compilation) => { return new Promise(async (resolve, reject) => { try { - let data = await fs.readFileSync(path.join(config.templatesDir, './app-template.js')); + let data = await fs.readFileSync(path.join(compilation.context.templatesDir, `${compilation.context.appTemplate}`)); const routes = compilation.graph.map(file => { if (file.route !== '/') { @@ -74,7 +78,8 @@ const writeRoutes = async(config, compilation) => { const result = data.toString().replace(/MYROUTES/g, routes.join('')); - await fs.writeFileSync(path.join(config.scratchDir, 'app', './app.js'), result); + // TODO magic strings, app and app.js + await fs.writeFileSync(path.join(compilation.context.scratchDir, 'app', './app.js'), result); resolve(); } catch (err) { @@ -84,15 +89,21 @@ const writeRoutes = async(config, compilation) => { }; // eslint-disable-next-line no-unused-vars -const setupIndex = async(config, compilation) => { +const setupIndex = async(compilation) => { return new Promise(async (resolve, reject) => { + const context = compilation.context; + let indexHtml = 'index.html'; + let notFoundHtml = '404.html'; + try { if (process.env.NODE_ENV === 'development') { - fs.copyFileSync(path.resolve(config.templatesDir, '404.dev.html'), path.join(config.scratchDir, '404.dev.html')); - fs.copyFileSync(path.resolve(config.templatesDir, 'index.dev.html'), path.join(config.scratchDir, 'index.dev.html')); + // TODO magic strings, html files + indexHtml = 'index.dev.html'; + notFoundHtml = '404.dev.html'; } - fs.copyFileSync(path.resolve(config.templatesDir, '404.html'), path.join(config.scratchDir, '404.html')); - fs.copyFileSync(path.resolve(config.templatesDir, 'index.html'), path.join(config.scratchDir, 'index.html')); + + fs.copyFileSync(path.resolve(context.templatesDir, notFoundHtml), path.join(context.scratchDir, notFoundHtml)); + fs.copyFileSync(path.resolve(context.templatesDir, indexHtml), path.join(context.scratchDir, indexHtml)); resolve(); } catch (err) { reject(err); @@ -100,20 +111,20 @@ const setupIndex = async(config, compilation) => { }); }; -module.exports = generateScaffolding = async (config, compilation) => { +module.exports = generateScaffolding = async (compilation) => { return new Promise(async (resolve, reject) => { try { console.log('Generate pages from templates...'); - await writePageComponentsFromTemplate(config, compilation); + await writePageComponentsFromTemplate(compilation); console.log('Writing imports for md...'); - await writeListImportFile(config, compilation); + await writeListImportFile(compilation); console.log('Writing Lit routes...'); - await writeRoutes(config, compilation); + await writeRoutes(compilation); console.log('setup index page and html'); - await setupIndex(config, compilation); + await setupIndex(compilation); console.log('Scaffolding complete.'); resolve(); diff --git a/packages/cli/lib/serialize.js b/packages/cli/lib/serialize.js index bd4462943..282fcbfdf 100644 --- a/packages/cli/lib/serialize.js +++ b/packages/cli/lib/serialize.js @@ -1,16 +1,15 @@ const LocalWebServer = require('local-web-server'); -const path = require('path'); const browserRunner = require('./util/browser'); const localWebServer = new LocalWebServer(); const PORT = '8000'; -const runBrowser = async (config, compilation) => { +const runBrowser = async (compilation) => { try { return await Promise.all(compilation.graph.map(file => { const route = file.route === '/' ? '' : file.route; - return browserRunner(`http://127.0.0.1:${PORT}${route}`, file.label, file.route, config.publicDir); + return browserRunner(`http://127.0.0.1:${PORT}/${route}`, file.label, file.route, compilation.context.publicDir); })); } catch (err) { // eslint-disable-next-line no-console @@ -19,18 +18,18 @@ const runBrowser = async (config, compilation) => { } }; -module.exports = serializeBuild = async (config, compilation) => { +module.exports = serializeBuild = async (compilation) => { return new Promise(async (resolve, reject) => { try { // "serialize" our SPA into a static site const server = localWebServer.listen({ port: PORT, https: false, - directory: path.join(config.publicDir), - spa: 'index.html' + directory: compilation.context.publicDir, + spa: 'index.html' // TODO magic string }); - await runBrowser(config, compilation); + await runBrowser(compilation); server.close(); // eslint-disable-next-line no-process-exit diff --git a/packages/cli/tasks/build.js b/packages/cli/tasks/build.js index 41776ec0f..42840ebb0 100644 --- a/packages/cli/tasks/build.js +++ b/packages/cli/tasks/build.js @@ -1,19 +1,16 @@ const path = require('path'); const webpack = require('webpack'); -const webpackConfig = require(path.join(__dirname, '..', './config/webpack.config.prod.js')); +const webpackMerge = require('webpack-merge'); const serializeBuild = require('../lib/serialize'); -const generateBuild = require('../lib/generate'); -module.exports = runProductionBuild = async() => { +module.exports = runProductionBuild = async(compilation) => { return new Promise(async (resolve, reject) => { - try { - const { config, compilation } = await generateBuild(); + try { + console.log('Building SPA from compilation...'); + await runWebpack(compilation); + await serializeBuild(compilation); - console.log('Build SPA from scaffolding...'); - // build our SPA application first - await buildCompilation(config, compilation); - await serializeBuild(config, compilation); resolve(); } catch (err) { reject(err); @@ -22,9 +19,13 @@ module.exports = runProductionBuild = async() => { }; // eslint-disable-next-line no-unused-vars -const buildCompilation = async () => { +const runWebpack = async ({ context }) => { + const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js'))(context); + const prodConfig = require(path.join(__dirname, '..', './config/webpack.config.prod.js'))(context); + const webpackConfig = webpackMerge(commonConfig, prodConfig); + return new Promise(async (resolve, reject) => { - + try { return webpack(webpackConfig, (err, stats) => { if (err || stats.hasErrors()) { diff --git a/packages/cli/tasks/develop.js b/packages/cli/tasks/develop.js index 44bf4468a..20a46e4ab 100644 --- a/packages/cli/tasks/develop.js +++ b/packages/cli/tasks/develop.js @@ -2,7 +2,7 @@ const path = require('path'); const webpack = require('webpack'); const webpackDevConfig = require(path.join(__dirname, '..', './config/webpack.config.develop.js')); const WebpackDevServer = require('webpack-dev-server'); -const generateBuild = require('../lib/generate'); +// const generateCompilation = require('../lib/compile'); module.exports = runDevServer = async () => { return new Promise(async (resolve, reject) => { @@ -10,7 +10,7 @@ module.exports = runDevServer = async () => { process.env.NODE_ENV = 'development'; try { - await generateBuild(); + // await generateBuild(); const serverConfig = webpackDevConfig.devServer; let compiler = webpack(webpackDevConfig); From 859240326b3fd035b729fa3df0ab12faf27173e4 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 17 Apr 2019 19:23:38 -0400 Subject: [PATCH 02/11] spec refactoring --- package.json | 2 +- packages/cli/config/webpack.config.develop.js | 2 + packages/cli/lib/init.js | 28 ++- test/cli.spec.js | 189 ++++++++++-------- 4 files changed, 128 insertions(+), 93 deletions(-) diff --git a/package.json b/package.json index 52ba1aeec..982df33a9 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build": "node ./packages/cli/index.js build", "serve": "yarn clean && yarn build && cd ./public && ws", "develop": "node ./packages/cli/index.js develop", - "test": "mocha --timeout 15000" + "test": "yarn clean && mocha --timeout 15000" }, "dependencies": { "@babel/core": "^7.4.0", diff --git a/packages/cli/config/webpack.config.develop.js b/packages/cli/config/webpack.config.develop.js index 04c1b54b3..f7c02e7a5 100644 --- a/packages/cli/config/webpack.config.develop.js +++ b/packages/cli/config/webpack.config.develop.js @@ -15,8 +15,10 @@ let isRebuilding = false; const rebuild = async() => { if (!isRebuilding) { isRebuilding = true; + // rebuild web components await generateCompilation(); + // debounce setTimeout(() => { isRebuilding = false; diff --git a/packages/cli/lib/init.js b/packages/cli/lib/init.js index e84847afb..fad914902 100644 --- a/packages/cli/lib/init.js +++ b/packages/cli/lib/init.js @@ -1,17 +1,18 @@ const fs = require('fs'); const path = require('path'); +const greenwoodWorkspace = path.join(__dirname, '..'); const userWorkspace = fs.existsSync(path.join(process.cwd(), 'src')) ? path.join(process.cwd(), 'src') - : path.join(__dirname, '..', 'templates/'); + : path.join(greenwoodWorkspace, 'templates/'); -const pagesDir = fs.existsSync(path.join(process.cwd(), 'src', 'pages')) - ? path.join(process.cwd(), 'src', 'pages/') - : path.join(__dirname, '..', 'templates/'); +const pagesDir = fs.existsSync(path.join(userWorkspace, 'pages')) + ? path.join(userWorkspace, 'pages/') + : path.join(greenwoodWorkspace, 'templates/'); -const templatesDir = fs.existsSync(path.join(process.cwd(), 'src', 'templates')) - ? path.join(process.cwd(), 'src', 'templates/') - : path.join(__dirname, '..', 'templates/'); +const templatesDir = fs.existsSync(path.join(userWorkspace, 'templates')) + ? path.join(userWorkspace, 'templates/') + : path.join(greenwoodWorkspace, 'templates/'); module.exports = initContexts = async() => { @@ -28,19 +29,24 @@ module.exports = initContexts = async() => { // default: true }; - if (fs.existsSync(templatesDir)) { - if (!fs.existsSync(path.join(templatesDir, context.pageTemplate))) { + // TODO allow per template overrides + if (fs.existsSync(context.templatesDir)) { + + // https://github.com/ProjectEvergreen/greenwood/issues/30 + if (!fs.existsSync(path.join(context.templatesDir, context.pageTemplate))) { reject('It looks like you don\'t have a page template defined. \n' + 'Please include a page-template.js in your templates directory. \n' + 'See https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/templates/page-template.js'); } - if (!fs.existsSync(path.join(templatesDir, context.appTemplate))) { + + // https://github.com/ProjectEvergreen/greenwood/issues/32 + if (!fs.existsSync(path.join(context.templatesDir, context.appTemplate))) { reject('It looks like you don\'t have an app template defined. \n' + 'Please include an app-template.js in your templates directory. \n' + 'See https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/templates/app-template.js'); } - /// set default flag that we're using a user template + // set default flag that we're using a user template // config.default = false; } if (!fs.existsSync(context.scratchDir)) { diff --git a/test/cli.spec.js b/test/cli.spec.js index 4bb986ba6..856ec6a44 100644 --- a/test/cli.spec.js +++ b/test/cli.spec.js @@ -16,89 +16,82 @@ const CONFIG = { usrTemplate: path.join(__dirname, '..', 'src', 'templates') }; -const whenSerialized = (head, par) => { - describe('when serialized', async () => { - let dom; - - before(async() => { - dom = await JSDOM.fromFile(path.resolve(__dirname, '..', './public/hello/index.html')); - }); +describe('building greenwood with default context (no user workspace)', () => { + + beforeEach(async () => { + setup = new TestSetup(); + await setup.run(['./packages/cli/index.js', 'build']); + }); - it('should display the hello world heading', async () => { - let heading = dom.window.document.querySelector('h3.wc-md-hello').textContent; + it('should create a public directory', () => { + expect(fs.existsSync(CONFIG.publicDir)).to.be.true; + }); - expect(heading).to.equal(head); + describe('public directory output', () => { + it('should output a single index.html file (home page)', () => { + expect(fs.existsSync(path.join(CONFIG.publicDir, './index.html'))).to.be.true; }); - - it('should display the hello world text', async () => { - let paragraph = dom.window.document.querySelector('p.wc-md-hello').textContent; - - expect(paragraph).to.equal(par); + + it('should output one JS bundle file', async () => { + expect(await glob.promise(path.join(CONFIG.publicDir, './index.*.bundle.js'))).to.have.lengthOf(1); + }); + + it('should create a default hello page directory', () => { + expect(fs.existsSync(path.join(CONFIG.publicDir, './hello'))).to.be.true; }); - }); -}; -describe('after building greenwood', () => { + xdescribe('default generated hello page contents', () => { + const defaultHeading = 'Hello World'; + const defaultBody = 'This is an example page built by Greenwood. Make your own in src/pages!'; + let dom; - before(async () => { - setup = new TestSetup(); - }); - describe('with an empty user templates directory', () => { - beforeEach(async() => { - // create empty template directory - await fs.mkdirSync(CONFIG.usrSrc); - await fs.mkdirSync(CONFIG.usrTemplate); - }); + beforeEach(async() => { + dom = await JSDOM.fromFile(path.resolve(CONFIG.publicDir, 'hello/index.html')); + }); - it('should display an error if page-template.js is missing', async() => { - await setup.run(['./packages/cli/index.js'], '').catch((err) => { - expect(err).to.contain("It looks like you don't have a page template defined. "); + it('should output an index.html file within the default hello page directory', () => { + expect(fs.existsSync(path.join(CONFIG.publicDir, './hello', './index.html'))).to.be.true; }); - }); - it('should display an error if app-template.js is missing', async () => { - // add blank page-template - await fs.writeFileSync(path.join(CONFIG.usrTemplate, 'page-template.js'), ''); - await setup.run(['./packages/cli/index.js'], '').catch((err) => { - expect(err).to.contain("It looks like you don't have an app template defined. "); + it('should have the expected heading text within the hello example page in the hello directory', async() => { + const heading = dom.window.document.querySelector('h3.wc-md-hello').textContent; + + expect(heading).to.equal(defaultHeading); + }); + + it('should have the expected heading text within the hello example page in the hello directory', async() => { + let paragraph = dom.window.document.querySelector('p.wc-md-hello').textContent; + + expect(paragraph).to.equal(defaultBody); }); }); + }); - afterEach(async () => { - await fs.remove(CONFIG.usrSrc); - await fs.remove(CONFIG.scratchDir); - }); + afterEach(async() => { + await fs.remove(CONFIG.usrSrc); + await fs.remove(CONFIG.publicDir); + await fs.remove(CONFIG.scratchDir); }); }); -describe('after building greenwood', () => { +xdescribe('building greenwood with a user workspace w/custom and nested pages directories', () => { - before(async () => { - setup = new TestSetup(); + beforeEach(async() => { + // copy test app + await fs.copy(CONFIG.testApp, CONFIG.usrSrc); await setup.run(['./packages/cli/index.js', 'build']); - }); + }); - it('should create a new public directory', () => { - expect(fs.existsSync(CONFIG.publicDir)).to.be.true; + it('should contain a nested blog page directory', () => { + expect(fs.existsSync(path.join(CONFIG.publicDir, 'blog', '20190326'))).to.be.true; }); - describe('within the public folder', () => { - it('should contain an index.html file', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, './index.html'))).to.be.true; - }); - it('should contain a js bundle file', async () => { - expect(await glob.promise(path.join(CONFIG.publicDir, './index.*.bundle.js'))).to.have.lengthOf(1); - }); - it('should contain a hello directory', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, './hello'))).to.be.true; - }); - it('should contain an index.html file within the hello world directory', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, './hello', './index.html'))).to.be.true; - }); + it('should contain a nested blog page with an index html file', () => { + expect(fs.existsSync(path.join(CONFIG.publicDir, 'blog', '20190326', 'index.html'))).to.be.true; }); - describe('using default greenwood template', () => { + it('should have the expected text within the hello world example page in the hello world directory', () => { whenSerialized('Hello World', 'This is an example page built by Greenwood. Make your own in src/pages!'); after(async() => { await fs.remove(CONFIG.publicDir); @@ -106,27 +99,61 @@ describe('after building greenwood', () => { }); }); - describe('using a user workspace directory', () => { - before(async() => { - // copy test app - await fs.copy(CONFIG.testApp, CONFIG.usrSrc); - await setup.run(['./packages/cli/index.js', 'build']); - }); - describe('with a correct user templates directory', () => { - whenSerialized('Test App', 'This is a test app using a custom user template!'); - }); - describe('with a nested page directory', () => { - it('should contain a nested blog directory', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, 'blog', '20190326'))).to.be.true; - }); - it('should contain a nested blog index html file', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, 'blog', '20190326', 'index.html'))).to.be.true; - }); + // TODO + // it('should have X number of JS bundles', () => { + + // }); + + // TODO + // it('should have other things to test for?', () => { + + // }); + + afterEach(async() => { + await fs.remove(CONFIG.usrSrc); + await fs.remove(CONFIG.publicDir); + await fs.remove(CONFIG.scratchDir); + }); + +}); + +// TODO - https://github.com/ProjectEvergreen/greenwood/issues/32 +// describe('building greenwood with a user workspace w/custom app-template override', () => { + +// }); + +// TODO - https://github.com/ProjectEvergreen/greenwood/issues/30 +// describe('building greenwood with a user workspace w/custom page-template override', () => { + +// }); + +xdescribe('building greenwood with error handling for app and page templates', () => { + beforeEach(async () => { + setup = new TestSetup(); + + // create empty template directory + await fs.mkdirSync(CONFIG.usrSrc); + await fs.mkdirSync(CONFIG.usrTemplate); + }); + + it('should display an error if page-template.js is missing', async() => { + await setup.run(['./packages/cli/index.js'], '').catch((err) => { + expect(err).to.contain("It looks like you don't have a page template defined. "); }); - after(async() => { - await fs.remove(CONFIG.usrSrc); - await fs.remove(CONFIG.publicDir); - await fs.remove(CONFIG.scratchDir); + }); + + it('should display an error if app-template.js is missing', async () => { + // add blank page-template + await fs.writeFileSync(path.join(CONFIG.usrTemplate, 'page-template.js'), ''); + await setup.run(['./packages/cli/index.js'], '').catch((err) => { + expect(err).to.contain("It looks like you don't have an app template defined. "); }); - }); + }); + + afterEach(async() => { + await fs.remove(CONFIG.usrSrc); + await fs.remove(CONFIG.publicDir); + await fs.remove(CONFIG.scratchDir); + }); + }); \ No newline at end of file From 6367490bc21284bfdd6fd9fbc58e6ce5af37b0be Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 17 Apr 2019 19:25:46 -0400 Subject: [PATCH 03/11] enable more tests --- test/cli.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cli.spec.js b/test/cli.spec.js index 856ec6a44..d40b00c6e 100644 --- a/test/cli.spec.js +++ b/test/cli.spec.js @@ -40,7 +40,7 @@ describe('building greenwood with default context (no user workspace)', () => { expect(fs.existsSync(path.join(CONFIG.publicDir, './hello'))).to.be.true; }); - xdescribe('default generated hello page contents', () => { + describe('default generated hello page directory', () => { const defaultHeading = 'Hello World'; const defaultBody = 'This is an example page built by Greenwood. Make your own in src/pages!'; let dom; @@ -59,7 +59,7 @@ describe('building greenwood with default context (no user workspace)', () => { expect(heading).to.equal(defaultHeading); }); - it('should have the expected heading text within the hello example page in the hello directory', async() => { + it('should have the expected paragraph text within the hello example page in the hello directory', async() => { let paragraph = dom.window.document.querySelector('p.wc-md-hello').textContent; expect(paragraph).to.equal(defaultBody); From 4582e940ace00631268c4153303ea3adcad9e9d8 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 17 Apr 2019 19:41:16 -0400 Subject: [PATCH 04/11] enable more tests --- test/cli.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli.spec.js b/test/cli.spec.js index d40b00c6e..bb9fab049 100644 --- a/test/cli.spec.js +++ b/test/cli.spec.js @@ -127,7 +127,7 @@ xdescribe('building greenwood with a user workspace w/custom and nested pages di // }); -xdescribe('building greenwood with error handling for app and page templates', () => { +describe('building greenwood with error handling for app and page templates', () => { beforeEach(async () => { setup = new TestSetup(); From 4853c4e78e24ba283c427fa48e4401eb4d3846dc Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 17 Apr 2019 20:17:56 -0400 Subject: [PATCH 05/11] all tests passing --- .github/CONTRIBUTING.md | 6 ++- packages/cli/lib/graph.js | 2 +- packages/cli/lib/scaffold.js | 5 ++- test/cli.spec.js | 44 ++++++++++++------- .../mock-app/src/pages/blog/20190326/index.md | 4 +- 5 files changed, 38 insertions(+), 23 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f74fb8394..2180dd649 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -26,4 +26,8 @@ To develop for the project, you'll want to follow these steps: Unit tests have been written that can be run using ```shell $ yarn test -``` \ No newline at end of file +``` + +Note, you can use the following to adjust how many mocha tests get run: +- `describe.only` / `it.only`: only run this block +- `xdescribe` / `xit`: dont run this block \ No newline at end of file diff --git a/packages/cli/lib/graph.js b/packages/cli/lib/graph.js index 2979cc510..0e865a534 100644 --- a/packages/cli/lib/graph.js +++ b/packages/cli/lib/graph.js @@ -56,7 +56,7 @@ const createGraphFromPages = async (pagesDir) => { // set route to the nested pages path and file name(without extension) route = completeNestedPath + route; mdFile = `./${completeNestedPath}${fileRoute}.md`; - relativeExpectedPath = `'..${completeNestedPath}/${fileName}/${fileName}.js'`; + relativeExpectedPath = `'../${completeNestedPath}/${fileName}/${fileName}.js'`; } else { mdFile = `./${fileRoute}.md`; relativeExpectedPath = `'../${fileName}/${fileName}.js'`; diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 3a65b9cfe..982b3ea17 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -49,7 +49,7 @@ const writePageComponentsFromTemplate = async (compilation) => { }; const writeListImportFile = async (compilation) => { - let arr = compilation.graph.map(file => { + const importList = compilation.graph.map(file => { return `import ${file.relativeExpectedPath};\n`; }); @@ -61,8 +61,9 @@ const writeListImportFile = async (compilation) => { await fs.mkdirSync(appDir); } + console.log('importList', importList); // TODO magic string - list.js - return await fs.writeFileSync(path.join(appDir, './list.js'), arr.join('')); + return await fs.writeFileSync(path.join(appDir, './list.js'), importList.join('')); }; const writeRoutes = async(compilation) => { diff --git a/test/cli.spec.js b/test/cli.spec.js index bb9fab049..9d01353ed 100644 --- a/test/cli.spec.js +++ b/test/cli.spec.js @@ -75,39 +75,49 @@ describe('building greenwood with default context (no user workspace)', () => { }); -xdescribe('building greenwood with a user workspace w/custom and nested pages directories', () => { +describe('building greenwood with a user workspace w/custom nested pages directories', () => { beforeEach(async() => { + setup = new TestSetup(); // copy test app await fs.copy(CONFIG.testApp, CONFIG.usrSrc); await setup.run(['./packages/cli/index.js', 'build']); }); + it('should output one JS bundle', async() => { + expect(await glob.promise(path.join(CONFIG.publicDir, './**/index.*.bundle.js'))).to.have.lengthOf(1); + }); + it('should contain a nested blog page directory', () => { expect(fs.existsSync(path.join(CONFIG.publicDir, 'blog', '20190326'))).to.be.true; }); - it('should contain a nested blog page with an index html file', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, 'blog', '20190326', 'index.html'))).to.be.true; - }); + describe('nested generated blog page directory', () => { + const defaultHeading = 'Blog Page'; + const defaultBody = 'This is the blog page built by Greenwood.'; + const blogPageHtmlPath = path.join(CONFIG.publicDir, 'blog', '20190326', 'index.html'); + let dom; - it('should have the expected text within the hello world example page in the hello world directory', () => { - whenSerialized('Hello World', 'This is an example page built by Greenwood. Make your own in src/pages!'); - after(async() => { - await fs.remove(CONFIG.publicDir); - await fs.remove(CONFIG.scratchDir); + beforeEach(async() => { + dom = await JSDOM.fromFile(blogPageHtmlPath); }); - }); - // TODO - // it('should have X number of JS bundles', () => { + it('should contain a nested blog page with an index html file', () => { + expect(fs.existsSync(blogPageHtmlPath)).to.be.true; + }); - // }); + it('should have the expected heading text within the blog page in the blog directory', async() => { + const heading = dom.window.document.querySelector('h3.wc-md-blog').textContent; - // TODO - // it('should have other things to test for?', () => { - - // }); + expect(heading).to.equal(defaultHeading); + }); + + it('should have the expected paragraph text within the blog page in the blog directory', async() => { + let paragraph = dom.window.document.querySelector('p.wc-md-blog').textContent; + + expect(paragraph).to.equal(defaultBody); + }); + }); afterEach(async() => { await fs.remove(CONFIG.usrSrc); diff --git a/test/fixtures/mock-app/src/pages/blog/20190326/index.md b/test/fixtures/mock-app/src/pages/blog/20190326/index.md index 6642ab4a9..9b8432f3f 100644 --- a/test/fixtures/mock-app/src/pages/blog/20190326/index.md +++ b/test/fixtures/mock-app/src/pages/blog/20190326/index.md @@ -6,9 +6,9 @@ imports: CSS: '../../../styles/theme.css' --- -### Blog +### Blog Page -This is the home page built by Greenwood. Make your own pages in src/pages/index.js! +This is the blog page built by Greenwood. ```render From d7310b859e71c10e6505da13b600b9446164f5e1 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 17 Apr 2019 20:18:42 -0400 Subject: [PATCH 06/11] remove comment --- packages/cli/lib/scaffold.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 982b3ea17..00c8e8b01 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -61,7 +61,6 @@ const writeListImportFile = async (compilation) => { await fs.mkdirSync(appDir); } - console.log('importList', importList); // TODO magic string - list.js return await fs.writeFileSync(path.join(appDir, './list.js'), importList.join('')); }; From a8dca41d5a7ae77c6a0e3704264adeea7023169e Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 17 Apr 2019 20:54:34 -0400 Subject: [PATCH 07/11] develop task working --- package.json | 2 +- packages/cli/config/webpack.config.develop.js | 11 ++++------- packages/cli/index.js | 17 ++++++++++++----- packages/cli/lib/scaffold.js | 10 +++++++--- packages/cli/tasks/develop.js | 19 +++++++++---------- 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 982df33a9..312b27ffa 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint": "eslint \"./packages/**/**/*.js\" \"./test/**/**/*.js\"", "build": "node ./packages/cli/index.js build", "serve": "yarn clean && yarn build && cd ./public && ws", - "develop": "node ./packages/cli/index.js develop", + "develop": "yarn clean && node ./packages/cli/index.js develop", "test": "yarn clean && mocha --timeout 15000" }, "dependencies": { diff --git a/packages/cli/config/webpack.config.develop.js b/packages/cli/config/webpack.config.develop.js index f7c02e7a5..06edd163b 100644 --- a/packages/cli/config/webpack.config.develop.js +++ b/packages/cli/config/webpack.config.develop.js @@ -1,7 +1,4 @@ -// const fs = require('fs'); const path = require('path'); -// const commonConfig = require('./webpack.config.common'); -// const webpackMerge = require('webpack-merge'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); const FilewatcherPlugin = require('filewatcher-webpack-plugin'); @@ -9,7 +6,7 @@ const generateCompilation = require('../lib/compile'); const host = 'localhost'; const port = 1981; -const publicPath = '/'; // commonConfig.publicPath; +const publicPath = '/'; let isRebuilding = false; const rebuild = async() => { @@ -26,7 +23,7 @@ const rebuild = async() => { } }; -module.exports = getDevelopConfig = (context) => { +module.exports = (context) => { return { mode: 'development', @@ -67,13 +64,13 @@ module.exports = getDevelopConfig = (context) => { // TODO magic string paths (index.html) new HtmlWebpackPlugin({ filename: 'index.html', - template: '.greenwood/index.dev.html', + template: path.join(context.scratchDir, 'index.dev.html'), publicPath }), // TODO magic string paths (404.html) new HtmlWebpackPlugin({ filename: '404.html', - template: '.greenwood/404.dev.html', + template: path.join(context.scratchDir, '404.dev.html'), publicPath }) ] diff --git a/packages/cli/index.js b/packages/cli/index.js index 07c586d36..5694a42fc 100644 --- a/packages/cli/index.js +++ b/packages/cli/index.js @@ -51,25 +51,32 @@ if (program.parse.length === 0) { } const run = async() => { - // 1) init context - // 2) generate graph - // 3) scaffolding - // TODO distinguish compilation from scaffolding? + process.env.NODE_ENV = MODE === 'develop' ? 'development' : 'production'; + const compilation = await generateCompilation(); try { + switch (MODE) { case 'build': + console.log('Building project for production.'.yellow); + await runProdBuild(compilation); + console.log('...................................'.yellow); console.log('Static site generation complete!'); console.log('Serve with: '.cyan + 'greenwood serve'.green); console.log('...................................'.yellow); + break; case 'develop': - console.log('Development Mode Activated'); + console.log('Starting local development server'.yellow); + await runDevServer(compilation); + + console.log('Development mode activiated'.green); + break; case 'create': console.log('Creating Greenwood application...'); diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 00c8e8b01..9d3339f32 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -94,16 +94,20 @@ const setupIndex = async(compilation) => { const context = compilation.context; let indexHtml = 'index.html'; let notFoundHtml = '404.html'; + let devIndexHtml = 'index.dev.html'; + let devNotFoundHtml = '404.dev.html'; try { + + // create redirect 404 pages for lit-redux-router + SPA fallback for development if (process.env.NODE_ENV === 'development') { - // TODO magic strings, html files - indexHtml = 'index.dev.html'; - notFoundHtml = '404.dev.html'; + fs.copyFileSync(path.resolve(context.templatesDir, devNotFoundHtml), path.join(context.scratchDir, devNotFoundHtml)); + fs.copyFileSync(path.resolve(context.templatesDir, devIndexHtml), path.join(context.scratchDir, devIndexHtml)); } fs.copyFileSync(path.resolve(context.templatesDir, notFoundHtml), path.join(context.scratchDir, notFoundHtml)); fs.copyFileSync(path.resolve(context.templatesDir, indexHtml), path.join(context.scratchDir, indexHtml)); + resolve(); } catch (err) { reject(err); diff --git a/packages/cli/tasks/develop.js b/packages/cli/tasks/develop.js index 20a46e4ab..b898dd4dc 100644 --- a/packages/cli/tasks/develop.js +++ b/packages/cli/tasks/develop.js @@ -1,22 +1,21 @@ const path = require('path'); const webpack = require('webpack'); -const webpackDevConfig = require(path.join(__dirname, '..', './config/webpack.config.develop.js')); const WebpackDevServer = require('webpack-dev-server'); -// const generateCompilation = require('../lib/compile'); +const webpackMerge = require('webpack-merge'); -module.exports = runDevServer = async () => { +module.exports = runDevServer = async ({ context }) => { return new Promise(async (resolve, reject) => { - - process.env.NODE_ENV = 'development'; try { - // await generateBuild(); + const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js'))(context); + const devConfig = require(path.join(__dirname, '..', './config/webpack.config.develop.js'))(context); + const webpackConfig = webpackMerge(commonConfig, devConfig); + const devServerConfig = webpackConfig.devServer; - const serverConfig = webpackDevConfig.devServer; - let compiler = webpack(webpackDevConfig); - let webpackServer = new WebpackDevServer(compiler, serverConfig); + let compiler = webpack(webpackConfig); + let webpackServer = new WebpackDevServer(compiler, devServerConfig); - webpackServer.listen(serverConfig.port); + webpackServer.listen(devServerConfig.port); } catch (err) { reject(err); } From e1943b7c065651264d4f66cf345dcb2bb01d98aa Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Thu, 18 Apr 2019 07:02:49 -0400 Subject: [PATCH 08/11] refactor webpack common / prod config --- packages/cli/config/webpack.config.common.js | 44 +++++++++-------- packages/cli/config/webpack.config.develop.js | 2 +- packages/cli/config/webpack.config.prod.js | 47 +++++-------------- packages/cli/index.js | 21 --------- packages/cli/lib/init.js | 4 -- packages/cli/lib/scaffold.js | 3 -- packages/cli/lib/serialize.js | 2 +- packages/cli/tasks/build.js | 5 +- packages/cli/tasks/serve.js | 3 -- test/cli.spec.js | 46 +++++++++--------- 10 files changed, 58 insertions(+), 119 deletions(-) delete mode 100644 packages/cli/tasks/serve.js diff --git a/packages/cli/config/webpack.config.common.js b/packages/cli/config/webpack.config.common.js index af6ac8282..568ae9ae2 100644 --- a/packages/cli/config/webpack.config.common.js +++ b/packages/cli/config/webpack.config.common.js @@ -7,39 +7,37 @@ const isDirectory = source => fs.lstatSync(source).isDirectory(); const getUserWorkspaceDirectories = (source) => { return fs.readdirSync(source).map(name => path.join(source, name)).filter(isDirectory); }; +const mapUserWorkspaceDirectory = (userPath) => { + const directory = userPath.split('/')[userPath.split('/').length - 1]; + + return new webpack.NormalModuleReplacementPlugin( + new RegExp(`${directory}`), + (resource) => { + resource.request = resource.request.replace(new RegExp(`\.\.\/${directory}`), userPath); + + // remove any additional nests, after replacement with absolute path of user workspace + directory + const additionalNestedPathIndex = resource.request.lastIndexOf('..'); + + if (additionalNestedPathIndex > -1) { + resource.request = resource.request.substring(additionalNestedPathIndex + 2, resource.request.length); + } + } + ); +}; module.exports = (context) => { // dynamically map all the user's workspace directories for resolution by webpack // this essentially helps us keep watch over changes from the user, and greenwood's build pipeline - // TODO make a function ? - const mappedUserDirectoriesForWebpack = getUserWorkspaceDirectories(context.userWorkspace).map((userPath) => { - const directory = userPath.split('/')[userPath.split('/').length - 1]; - - return new webpack.NormalModuleReplacementPlugin( - new RegExp(`${directory}`), - (resource) => { - resource.request = resource.request.replace(new RegExp(`\.\.\/${directory}`), userPath); - - // remove any additional nests, after replacement with absolute path of user workspace + directory - const additionalNestedPathIndex = resource.request.lastIndexOf('..'); - - if (additionalNestedPathIndex > -1) { - resource.request = resource.request.substring(additionalNestedPathIndex + 2, resource.request.length); - } - } - ); - }); + const mappedUserDirectoriesForWebpack = getUserWorkspaceDirectories(context.userWorkspace).map(mapUserWorkspaceDirectory); return { entry: { - // TODO magic string - greenwood, app, app.js - index: path.join(process.cwd(), '.greenwood', 'app', 'app.js') + index: path.join(context.scratchDir, 'app', 'app.js') }, output: { - // TODO magic string - public - path: path.join(process.cwd(), 'public'), + path: context.publicDir, filename: '[name].[hash].bundle.js', publicPath: '/' }, @@ -102,7 +100,7 @@ module.exports = (context) => { ), new HtmlWebpackPlugin({ - template: path.join(process.cwd(), '.greenwood', 'index.html'), + template: path.join(context.scratchDir, 'index.html'), chunksSortMode: 'dependency' }) ] diff --git a/packages/cli/config/webpack.config.develop.js b/packages/cli/config/webpack.config.develop.js index 06edd163b..81634c85f 100644 --- a/packages/cli/config/webpack.config.develop.js +++ b/packages/cli/config/webpack.config.develop.js @@ -31,7 +31,7 @@ module.exports = (context) => { // TODO magic strings - .greenwood, app, app.js entry: [ `webpack-dev-server/client?http://${host}:${port}`, - path.join(process.cwd(), '.greenwood', 'app', 'app.js') + path.join(context.scratchDir, 'app', 'app.js') ], devServer: { diff --git a/packages/cli/config/webpack.config.prod.js b/packages/cli/config/webpack.config.prod.js index 68b29d4d2..c79540f25 100644 --- a/packages/cli/config/webpack.config.prod.js +++ b/packages/cli/config/webpack.config.prod.js @@ -1,10 +1,11 @@ -// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; -// const FaviconsWebpackPlugin = require('favicons-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const path = require('path'); +const webpackMerge = require('webpack-merge'); -module.exports = () => { - - return { +module.exports = (context) => { + const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js'))(context); + + return webpackMerge(commonConfig, { mode: 'production', @@ -12,39 +13,13 @@ module.exports = () => { hints: 'error' }, - plugins: [ - // TODO magic strings 404 html + plugins: [ new HtmlWebpackPlugin({ filename: '404.html', - template: '.greenwood/404.html', - publicPath: '/' // TOOD reuse from common config + template: path.join(context.scratchDir, '404.html'), + publicPath: commonConfig.output.publicPath }) - // new FaviconsWebpackPlugin({ - // logo: './favicon.png', - // emitStats: true, - // prefix: 'icons/', - // statsFilename: 'icons/stats.json', - // inject: true, - // title: 'Create Evergreen App', - // background: '#466628', - // icons: { - // android: true, - // appleIcon: false, - // appleStartup: false, - // coast: false, - // favicons: true, - // firefox: true, - // opengraph: true, - // twitter: true, - // yandex: false, - // windows: false - // } - // }), - - // new BundleAnalyzerPlugin({ - // analyzerMode: 'static', - // openAnalyzer: false - // }) ] - }; + + }); }; \ No newline at end of file diff --git a/packages/cli/index.js b/packages/cli/index.js index 5694a42fc..8bc41a585 100644 --- a/packages/cli/index.js +++ b/packages/cli/index.js @@ -31,18 +31,6 @@ program .action((cmd) => { MODE = cmd._name; }); -program - .command('create') - .description('Generate a new static site.') - .action((cmd) => { - MODE = cmd._name; - }); -program - .command('serve') - .description('Serve a production build locally.') - .action((cmd) => { - MODE = cmd._name; - }); program.parse(process.argv); @@ -66,7 +54,6 @@ const run = async() => { console.log('...................................'.yellow); console.log('Static site generation complete!'); - console.log('Serve with: '.cyan + 'greenwood serve'.green); console.log('...................................'.yellow); break; @@ -77,14 +64,6 @@ const run = async() => { console.log('Development mode activiated'.green); - break; - case 'create': - console.log('Creating Greenwood application...'); - // Generate Greenwood application - break; - case 'serve': - console.log('Now serving application at http://localhost:8000'); - // Serve Greenwood application break; default: console.log('Error: missing command. try checking --help if you\'re encountering issues'); diff --git a/packages/cli/lib/init.js b/packages/cli/lib/init.js index fad914902..deeaf7813 100644 --- a/packages/cli/lib/init.js +++ b/packages/cli/lib/init.js @@ -26,7 +26,6 @@ module.exports = initContexts = async() => { publicDir: path.join(process.cwd(), './public'), pageTemplate: 'page-template.js', appTemplate: 'app-template.js' - // default: true }; // TODO allow per template overrides @@ -45,9 +44,6 @@ module.exports = initContexts = async() => { 'Please include an app-template.js in your templates directory. \n' + 'See https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/templates/app-template.js'); } - - // set default flag that we're using a user template - // config.default = false; } if (!fs.existsSync(context.scratchDir)) { fs.mkdirSync(context.scratchDir); diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 9d3339f32..e5333158a 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -54,14 +54,12 @@ const writeListImportFile = async (compilation) => { }); // Create app directory so that app-template relative imports are correct - // TODO magic string - app const appDir = path.join(compilation.context.scratchDir, 'app'); if (!fs.existsSync(appDir)) { await fs.mkdirSync(appDir); } - // TODO magic string - list.js return await fs.writeFileSync(path.join(appDir, './list.js'), importList.join('')); }; @@ -78,7 +76,6 @@ const writeRoutes = async(compilation) => { const result = data.toString().replace(/MYROUTES/g, routes.join('')); - // TODO magic strings, app and app.js await fs.writeFileSync(path.join(compilation.context.scratchDir, 'app', './app.js'), result); resolve(); diff --git a/packages/cli/lib/serialize.js b/packages/cli/lib/serialize.js index 282fcbfdf..95d86e205 100644 --- a/packages/cli/lib/serialize.js +++ b/packages/cli/lib/serialize.js @@ -26,7 +26,7 @@ module.exports = serializeBuild = async (compilation) => { port: PORT, https: false, directory: compilation.context.publicDir, - spa: 'index.html' // TODO magic string + spa: 'index.html' }); await runBrowser(compilation); diff --git a/packages/cli/tasks/build.js b/packages/cli/tasks/build.js index 42840ebb0..896797216 100644 --- a/packages/cli/tasks/build.js +++ b/packages/cli/tasks/build.js @@ -1,6 +1,5 @@ const path = require('path'); const webpack = require('webpack'); -const webpackMerge = require('webpack-merge'); const serializeBuild = require('../lib/serialize'); module.exports = runProductionBuild = async(compilation) => { @@ -20,9 +19,7 @@ module.exports = runProductionBuild = async(compilation) => { // eslint-disable-next-line no-unused-vars const runWebpack = async ({ context }) => { - const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js'))(context); - const prodConfig = require(path.join(__dirname, '..', './config/webpack.config.prod.js'))(context); - const webpackConfig = webpackMerge(commonConfig, prodConfig); + const webpackConfig = require(path.join(__dirname, '..', './config/webpack.config.prod.js'))(context); return new Promise(async (resolve, reject) => { diff --git a/packages/cli/tasks/serve.js b/packages/cli/tasks/serve.js deleted file mode 100644 index 9cbbffd06..000000000 --- a/packages/cli/tasks/serve.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = serveSite = async () => { - console.log('future webserver code'); -}; \ No newline at end of file diff --git a/test/cli.spec.js b/test/cli.spec.js index 9d01353ed..88fcb7bbb 100644 --- a/test/cli.spec.js +++ b/test/cli.spec.js @@ -6,7 +6,7 @@ const TestSetup = require('./setup'); const jsdom = require('jsdom'); const { JSDOM } = jsdom; -const CONFIG = { +const CONTEXT = { pagesDir: path.join(__dirname, '../packages/cli/templates/'), scratchDir: path.join(__dirname, '..', './.greenwood/'), templatesDir: path.join(__dirname, '../packages/cli/templates/'), @@ -24,20 +24,20 @@ describe('building greenwood with default context (no user workspace)', () => { }); it('should create a public directory', () => { - expect(fs.existsSync(CONFIG.publicDir)).to.be.true; + expect(fs.existsSync(CONTEXT.publicDir)).to.be.true; }); describe('public directory output', () => { it('should output a single index.html file (home page)', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, './index.html'))).to.be.true; + expect(fs.existsSync(path.join(CONTEXT.publicDir, './index.html'))).to.be.true; }); it('should output one JS bundle file', async () => { - expect(await glob.promise(path.join(CONFIG.publicDir, './index.*.bundle.js'))).to.have.lengthOf(1); + expect(await glob.promise(path.join(CONTEXT.publicDir, './index.*.bundle.js'))).to.have.lengthOf(1); }); it('should create a default hello page directory', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, './hello'))).to.be.true; + expect(fs.existsSync(path.join(CONTEXT.publicDir, './hello'))).to.be.true; }); describe('default generated hello page directory', () => { @@ -46,11 +46,11 @@ describe('building greenwood with default context (no user workspace)', () => { let dom; beforeEach(async() => { - dom = await JSDOM.fromFile(path.resolve(CONFIG.publicDir, 'hello/index.html')); + dom = await JSDOM.fromFile(path.resolve(CONTEXT.publicDir, 'hello/index.html')); }); it('should output an index.html file within the default hello page directory', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, './hello', './index.html'))).to.be.true; + expect(fs.existsSync(path.join(CONTEXT.publicDir, './hello', './index.html'))).to.be.true; }); it('should have the expected heading text within the hello example page in the hello directory', async() => { @@ -68,9 +68,9 @@ describe('building greenwood with default context (no user workspace)', () => { }); afterEach(async() => { - await fs.remove(CONFIG.usrSrc); - await fs.remove(CONFIG.publicDir); - await fs.remove(CONFIG.scratchDir); + await fs.remove(CONTEXT.usrSrc); + await fs.remove(CONTEXT.publicDir); + await fs.remove(CONTEXT.scratchDir); }); }); @@ -80,22 +80,22 @@ describe('building greenwood with a user workspace w/custom nested pages directo beforeEach(async() => { setup = new TestSetup(); // copy test app - await fs.copy(CONFIG.testApp, CONFIG.usrSrc); + await fs.copy(CONTEXT.testApp, CONTEXT.usrSrc); await setup.run(['./packages/cli/index.js', 'build']); }); it('should output one JS bundle', async() => { - expect(await glob.promise(path.join(CONFIG.publicDir, './**/index.*.bundle.js'))).to.have.lengthOf(1); + expect(await glob.promise(path.join(CONTEXT.publicDir, './**/index.*.bundle.js'))).to.have.lengthOf(1); }); it('should contain a nested blog page directory', () => { - expect(fs.existsSync(path.join(CONFIG.publicDir, 'blog', '20190326'))).to.be.true; + expect(fs.existsSync(path.join(CONTEXT.publicDir, 'blog', '20190326'))).to.be.true; }); describe('nested generated blog page directory', () => { const defaultHeading = 'Blog Page'; const defaultBody = 'This is the blog page built by Greenwood.'; - const blogPageHtmlPath = path.join(CONFIG.publicDir, 'blog', '20190326', 'index.html'); + const blogPageHtmlPath = path.join(CONTEXT.publicDir, 'blog', '20190326', 'index.html'); let dom; beforeEach(async() => { @@ -120,9 +120,9 @@ describe('building greenwood with a user workspace w/custom nested pages directo }); afterEach(async() => { - await fs.remove(CONFIG.usrSrc); - await fs.remove(CONFIG.publicDir); - await fs.remove(CONFIG.scratchDir); + await fs.remove(CONTEXT.usrSrc); + await fs.remove(CONTEXT.publicDir); + await fs.remove(CONTEXT.scratchDir); }); }); @@ -142,8 +142,8 @@ describe('building greenwood with error handling for app and page templates', () setup = new TestSetup(); // create empty template directory - await fs.mkdirSync(CONFIG.usrSrc); - await fs.mkdirSync(CONFIG.usrTemplate); + await fs.mkdirSync(CONTEXT.usrSrc); + await fs.mkdirSync(CONTEXT.usrTemplate); }); it('should display an error if page-template.js is missing', async() => { @@ -154,16 +154,16 @@ describe('building greenwood with error handling for app and page templates', () it('should display an error if app-template.js is missing', async () => { // add blank page-template - await fs.writeFileSync(path.join(CONFIG.usrTemplate, 'page-template.js'), ''); + await fs.writeFileSync(path.join(CONTEXT.usrTemplate, 'page-template.js'), ''); await setup.run(['./packages/cli/index.js'], '').catch((err) => { expect(err).to.contain("It looks like you don't have an app template defined. "); }); }); afterEach(async() => { - await fs.remove(CONFIG.usrSrc); - await fs.remove(CONFIG.publicDir); - await fs.remove(CONFIG.scratchDir); + await fs.remove(CONTEXT.usrSrc); + await fs.remove(CONTEXT.publicDir); + await fs.remove(CONTEXT.scratchDir); }); }); \ No newline at end of file From 57e6b23334f1d9a5fd9245d82e614b43f288c3f0 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Thu, 18 Apr 2019 07:07:48 -0400 Subject: [PATCH 09/11] refactor webpack common / develop config --- packages/cli/config/webpack.config.develop.js | 10 ++++++---- packages/cli/tasks/develop.js | 5 +---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/cli/config/webpack.config.develop.js b/packages/cli/config/webpack.config.develop.js index 81634c85f..d7415349a 100644 --- a/packages/cli/config/webpack.config.develop.js +++ b/packages/cli/config/webpack.config.develop.js @@ -3,10 +3,10 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); const FilewatcherPlugin = require('filewatcher-webpack-plugin'); const generateCompilation = require('../lib/compile'); +const webpackMerge = require('webpack-merge'); const host = 'localhost'; const port = 1981; -const publicPath = '/'; let isRebuilding = false; const rebuild = async() => { @@ -24,11 +24,13 @@ const rebuild = async() => { }; module.exports = (context) => { + const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js'))(context); + const publicPath = commonConfig.output.publicPath; + + return webpackMerge(commonConfig, { - return { mode: 'development', - // TODO magic strings - .greenwood, app, app.js entry: [ `webpack-dev-server/client?http://${host}:${port}`, path.join(context.scratchDir, 'app', 'app.js') @@ -74,5 +76,5 @@ module.exports = (context) => { publicPath }) ] - }; + }); }; \ No newline at end of file diff --git a/packages/cli/tasks/develop.js b/packages/cli/tasks/develop.js index b898dd4dc..7598b8151 100644 --- a/packages/cli/tasks/develop.js +++ b/packages/cli/tasks/develop.js @@ -1,15 +1,12 @@ const path = require('path'); const webpack = require('webpack'); const WebpackDevServer = require('webpack-dev-server'); -const webpackMerge = require('webpack-merge'); module.exports = runDevServer = async ({ context }) => { return new Promise(async (resolve, reject) => { try { - const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js'))(context); - const devConfig = require(path.join(__dirname, '..', './config/webpack.config.develop.js'))(context); - const webpackConfig = webpackMerge(commonConfig, devConfig); + const webpackConfig = require(path.join(__dirname, '..', './config/webpack.config.develop.js'))(context); const devServerConfig = webpackConfig.devServer; let compiler = webpack(webpackConfig); From 5ac1395167f0101eb5b4534a4b94443b41beaad6 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Thu, 18 Apr 2019 18:29:41 -0400 Subject: [PATCH 10/11] hoist common config require to top of file --- packages/cli/config/webpack.config.develop.js | 7 ++++--- packages/cli/config/webpack.config.prod.js | 10 ++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/cli/config/webpack.config.develop.js b/packages/cli/config/webpack.config.develop.js index d7415349a..51febd528 100644 --- a/packages/cli/config/webpack.config.develop.js +++ b/packages/cli/config/webpack.config.develop.js @@ -4,6 +4,7 @@ const ManifestPlugin = require('webpack-manifest-plugin'); const FilewatcherPlugin = require('filewatcher-webpack-plugin'); const generateCompilation = require('../lib/compile'); const webpackMerge = require('webpack-merge'); +const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js')); const host = 'localhost'; const port = 1981; @@ -24,10 +25,10 @@ const rebuild = async() => { }; module.exports = (context) => { - const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js'))(context); - const publicPath = commonConfig.output.publicPath; + const configWithContext = commonConfig(context); + const publicPath = configWithContext.output.publicPath; - return webpackMerge(commonConfig, { + return webpackMerge(configWithContext, { mode: 'development', diff --git a/packages/cli/config/webpack.config.prod.js b/packages/cli/config/webpack.config.prod.js index c79540f25..3aad4561f 100644 --- a/packages/cli/config/webpack.config.prod.js +++ b/packages/cli/config/webpack.config.prod.js @@ -1,11 +1,12 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); const webpackMerge = require('webpack-merge'); +const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js')); module.exports = (context) => { - const commonConfig = require(path.join(__dirname, '..', './config/webpack.config.common.js'))(context); - - return webpackMerge(commonConfig, { + const configWithContext = commonConfig(context); + + return webpackMerge(configWithContext, { mode: 'production', @@ -17,9 +18,10 @@ module.exports = (context) => { new HtmlWebpackPlugin({ filename: '404.html', template: path.join(context.scratchDir, '404.html'), - publicPath: commonConfig.output.publicPath + publicPath: configWithContext.output.publicPath }) ] }); + }; \ No newline at end of file From 7718f7094085378a4c99b31b1044386fd1c656bd Mon Sep 17 00:00:00 2001 From: Grant Hutchinson Date: Mon, 22 Apr 2019 15:39:11 -0400 Subject: [PATCH 11/11] test: consolidating package directory contexts (#51) --- packages/cli/lib/init.js | 16 ++++++---- test/cli.spec.js | 64 ++++++++++++++++++---------------------- test/setup.js | 20 +++++++++++++ 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/packages/cli/lib/init.js b/packages/cli/lib/init.js index deeaf7813..f3ca8b0af 100644 --- a/packages/cli/lib/init.js +++ b/packages/cli/lib/init.js @@ -1,23 +1,27 @@ const fs = require('fs'); const path = require('path'); const greenwoodWorkspace = path.join(__dirname, '..'); +const defaultTemplateDir = path.join(greenwoodWorkspace, 'templates/'); +const defaultSrc = path.join(process.cwd(), 'src'); -const userWorkspace = fs.existsSync(path.join(process.cwd(), 'src')) - ? path.join(process.cwd(), 'src') - : path.join(greenwoodWorkspace, 'templates/'); +const userWorkspace = fs.existsSync(defaultSrc) + ? defaultSrc + : defaultTemplateDir; const pagesDir = fs.existsSync(path.join(userWorkspace, 'pages')) ? path.join(userWorkspace, 'pages/') - : path.join(greenwoodWorkspace, 'templates/'); + : defaultTemplateDir; const templatesDir = fs.existsSync(path.join(userWorkspace, 'templates')) ? path.join(userWorkspace, 'templates/') - : path.join(greenwoodWorkspace, 'templates/'); + : defaultTemplateDir; module.exports = initContexts = async() => { - + return new Promise((resolve, reject) => { + try { + const context = { userWorkspace, pagesDir, diff --git a/test/cli.spec.js b/test/cli.spec.js index 88fcb7bbb..98f8915cd 100644 --- a/test/cli.spec.js +++ b/test/cli.spec.js @@ -6,20 +6,12 @@ const TestSetup = require('./setup'); const jsdom = require('jsdom'); const { JSDOM } = jsdom; -const CONTEXT = { - pagesDir: path.join(__dirname, '../packages/cli/templates/'), - scratchDir: path.join(__dirname, '..', './.greenwood/'), - templatesDir: path.join(__dirname, '../packages/cli/templates/'), - publicDir: path.join(__dirname, '..', './public'), - testApp: path.join(__dirname, 'fixtures', 'mock-app', 'src'), - usrSrc: path.join(__dirname, '..', 'src'), - usrTemplate: path.join(__dirname, '..', 'src', 'templates') -}; - describe('building greenwood with default context (no user workspace)', () => { - - beforeEach(async () => { + + before(async () => { setup = new TestSetup(); + CONTEXT = await setup.init(); + await setup.run(['./packages/cli/index.js', 'build']); }); @@ -67,8 +59,7 @@ describe('building greenwood with default context (no user workspace)', () => { }); }); - afterEach(async() => { - await fs.remove(CONTEXT.usrSrc); + after(async() => { await fs.remove(CONTEXT.publicDir); await fs.remove(CONTEXT.scratchDir); }); @@ -76,14 +67,17 @@ describe('building greenwood with default context (no user workspace)', () => { }); describe('building greenwood with a user workspace w/custom nested pages directories', () => { - - beforeEach(async() => { + + before(async() => { setup = new TestSetup(); + CONTEXT = await setup.init(); // copy test app - await fs.copy(CONTEXT.testApp, CONTEXT.usrSrc); + await fs.copy(CONTEXT.testApp, CONTEXT.userSrc); await setup.run(['./packages/cli/index.js', 'build']); + + blogPageHtmlPath = path.join(CONTEXT.publicDir, 'blog', '20190326', 'index.html'); }); - + it('should output one JS bundle', async() => { expect(await glob.promise(path.join(CONTEXT.publicDir, './**/index.*.bundle.js'))).to.have.lengthOf(1); }); @@ -91,13 +85,12 @@ describe('building greenwood with a user workspace w/custom nested pages directo it('should contain a nested blog page directory', () => { expect(fs.existsSync(path.join(CONTEXT.publicDir, 'blog', '20190326'))).to.be.true; }); - + describe('nested generated blog page directory', () => { const defaultHeading = 'Blog Page'; const defaultBody = 'This is the blog page built by Greenwood.'; - const blogPageHtmlPath = path.join(CONTEXT.publicDir, 'blog', '20190326', 'index.html'); let dom; - + beforeEach(async() => { dom = await JSDOM.fromFile(blogPageHtmlPath); }); @@ -119,31 +112,32 @@ describe('building greenwood with a user workspace w/custom nested pages directo }); }); - afterEach(async() => { - await fs.remove(CONTEXT.usrSrc); + after(async() => { + await fs.remove(CONTEXT.userSrc); await fs.remove(CONTEXT.publicDir); await fs.remove(CONTEXT.scratchDir); }); }); -// TODO - https://github.com/ProjectEvergreen/greenwood/issues/32 -// describe('building greenwood with a user workspace w/custom app-template override', () => { +// // TODO - https://github.com/ProjectEvergreen/greenwood/issues/32 +// // describe('building greenwood with a user workspace w/custom app-template override', () => { -// }); +// // }); -// TODO - https://github.com/ProjectEvergreen/greenwood/issues/30 -// describe('building greenwood with a user workspace w/custom page-template override', () => { +// // TODO - https://github.com/ProjectEvergreen/greenwood/issues/30 +// // describe('building greenwood with a user workspace w/custom page-template override', () => { -// }); +// // }); describe('building greenwood with error handling for app and page templates', () => { - beforeEach(async () => { + before(async () => { setup = new TestSetup(); + CONTEXT = await setup.init(); // create empty template directory - await fs.mkdirSync(CONTEXT.usrSrc); - await fs.mkdirSync(CONTEXT.usrTemplate); + await fs.mkdirSync(CONTEXT.userSrc); + await fs.mkdirSync(CONTEXT.userTemplates); }); it('should display an error if page-template.js is missing', async() => { @@ -154,14 +148,14 @@ describe('building greenwood with error handling for app and page templates', () it('should display an error if app-template.js is missing', async () => { // add blank page-template - await fs.writeFileSync(path.join(CONTEXT.usrTemplate, 'page-template.js'), ''); + await fs.writeFileSync(path.join(CONTEXT.userTemplates, 'page-template.js'), ''); await setup.run(['./packages/cli/index.js'], '').catch((err) => { expect(err).to.contain("It looks like you don't have an app template defined. "); }); }); - afterEach(async() => { - await fs.remove(CONTEXT.usrSrc); + after(async() => { + await fs.remove(CONTEXT.userSrc); await fs.remove(CONTEXT.publicDir); await fs.remove(CONTEXT.scratchDir); }); diff --git a/test/setup.js b/test/setup.js index d10dc8a21..f7a5d3821 100644 --- a/test/setup.js +++ b/test/setup.js @@ -1,11 +1,31 @@ const os = require('os'); const { spawn } = require('child_process'); +const path = require('path'); +const initContext = require('../packages/cli/lib/init'); module.exports = class Setup { constructor(enableStdOut) { this.enableStdOut = enableStdOut; // debugging tests } + init() { + return new Promise(async(resolve, reject) => { + try { + const ctx = await initContext(); + const context = { + ...ctx, + userSrc: path.join(__dirname, '..', 'src'), // static src + userTemplates: path.join(__dirname, '..', 'src', 'templates'), // static src/templates for testing empty templates dir, redundant in #38 + testApp: path.join(__dirname, 'fixtures', 'mock-app', 'src') + }; + + resolve(context); + } catch (err) { + reject(err); + } + }); + } + run(args) { return new Promise(async (resolve, reject) => { const command = os.platform() === 'win32' ? 'npm.cmd' : 'node';