// Node imports import path from 'path'; import process from "process"; import { exec } from "child_process"; // Rollup plugin imports import less from 'rollup-plugin-less'; import sourcemaps from 'rollup-plugin-sourcemaps'; import stringLoader from "rollup-plugin-string"; import jsonPlugin from "rollup-plugin-json"; // Build/dev imports import {Buildup, assetPlugin, babelPlugin, copyPlugin, handlebarsPlugin, minifyPlugin, commonJSPlugin} from "@cerxes/buildup"; // Util imports import readArgs from "./util/read-args"; // Control async function build(options) { // Build let error = false, result; let watch = options.get("watch","w"); let development = options.get("dev", "d"); let server = options.get("server", "s"); let minify = options.get("minify", "m"); if(minify===undefined){minify = !development;} try { let settings = { target: "web", dev: development, minify: minify, es6: false //development }; let builder = new Buildup(({meta: settings})=>({ outDir: "dist/" + settings.target, inDir: "src", clean: true, alias: [ ["common", "src/common"] ], sourcemap: settings.dev?"inline":true, // 'inline' tends to work more reliably in chrome than just true (which is default and should be preferred) plugins: [ // Import less as strings less({// TODO fork/clone the less plugin, it's npm package is annoyingly transpiled (requiring us to have a babel-runtime) and it has other minor issues... output: (args)=>args// This is how we avoid the .css file from being made, and it feels hacky }), // Resolve maps from node_modules sourcemaps(), // Resolve non-es6 node_module libraries commonJSPlugin({ include: /node_modules\/.*$/, exclude: /.*[\/\.]es6?[\/\.].*/ }), ], steps: (config)=>[ [ { // Site Build (www, targeting browsers) inDir: "src/www", outDir: config.outDir + (settings.outDir? "/"+settings.outDir : "")+"/www", plugins: [ ...config.plugins, // Add ability to pass non-js files through the build-system assetPlugin({ exclude: [/\.svg$/] }), // Load gcodes and svgs as strings stringLoader({ include: ['**/*.gcode', '**/*.svg'] }), // Transform source files with babel (uses src/.babelrc for configuration) babelPlugin({ "target":"source"// transform the source inputs (could leave this undefined, its the default) }), // Transform output files with babel for browser compatibility settings.es6? null : babelPlugin({ "target": "bundle", // only transform the output bundle, not the source inputs "presets": [ [ "@babel/preset-env", { "targets": { "browsers": [ "last 6 Chrome versions", "last 1 Edge versions", "last 2 Firefox versions", ] }, "modules": false // Leave module processing to the custom transform-es-module plugin } ] ], "plugins": [ "@babel/plugin-syntax-dynamic-import", "@cerxes/buildup/dist/babel-transform-es-modules" ], }), // Compress output settings.minify? minifyPlugin() : null, ], steps: (wwwConfig)=>[ // Serial compilation steps: // - Compile vendor bundle first (needed/used in the next chunks) [ { // array in, single out in: [ "@cerxes/cxs-ui", ], out: "lib/vendor.js", bundle:"flat", }, ], // - Copy polyfills Buildup.scanDir( "node_modules/@webcomponents/webcomponentsjs/bundles/", /(webcomponents-([\-a-zA-Z]+)\.js)$/ ).then(wcPolyfills=>wcPolyfills.map(polyfill=>({ meta: polyfill, in: polyfill.files, out: "lib/polyfills/[1]", plugins: [ copyPlugin() ] }))), // - Compile pages Buildup.scanDir( wwwConfig.resolveIn("pages"), // Scan directory src/pages for files /(.*)\.page\.js$/ // (...) RegEx capture-group feeds into chunk-meta step allowing us to use [1]...[n] in strings below ).then(pages=>pages.map(page=>({ meta: page, in: page.files, out: "[1].page.js", steps: (pageChunk)=>[ { in: { alias: "[1].polyfill-loader.virtual.js", // Passing in an alias within the src/ map will allow babel to find the appropriate .babelrc to transpile it content: `import {loadPolyfills} from "pages/polyfill-loader"; loadPolyfills();` }, out: "[1].loader.virtual.js", emit: false, steps: (polyloaderChunk)=>({ in: { alias: "[1].loader.virtual.js", // Passing in an alias within the src/ map will allow babel to find the appropriate .babelrc to transpile it content: `import {loadPage} from "pages/page-loader"; import(\`${"./" + path.basename(pageChunk.outPath)}\`).then(module=>loadPage(module));` // Loader-code }, out: "[1].loader.virtual.js", emit: false, steps: (loaderChunk)=>[ { // Feed the loader as input to html-generator in: "page.hbs", plugins: [handlebarsPlugin({ context: { polyfill: polyloaderChunk.compiled.code, pageCode: loaderChunk.compiled.code, es6: settings.es6 } })], out: "[1].html" } ] }) } ] }))), // - Copy assets Buildup.scanDir( wwwConfig.resolveIn(""), // Scan directory src/pages for files /(.*)\.(eot|svg|ttf|woff|png|jpg)$/ // (...) RegEx capture-group feeds into chunk-meta step allowing us to use [1]...[n] in strings below ).then(assets=>assets.map(asset=>({ meta: asset, in: asset.files[0], out: "[1].[2]", asset: true }))) ] } ], [ { // Server Build (targetting node-environment) inDir: "src/server", outDir: config.outDir + (settings.outDir? "/"+settings.outDir : ""), plugins: [ jsonPlugin({ // for tree-shaking, properties will be declared as // variables, using either `var` or `const` preferConst: true, // Default: false }), ...config.plugins, // Transform source files with babel (uses src/.babelrc for configuration) for nodejs compatibility babelPlugin({ "target":"source"// transform the source inputs (could leave this undefined, its the default) }), // Transform output files with babel for nodejs compatibility babelPlugin({ "target": "bundle", // only transform the output bundle, not the source inputs "presets": [ ["@babel/preset-env", { "targets": { "node": "current" }, }] ], }), // Compress output settings.minify? minifyPlugin() : null, ], nodeResolve: { browser: false, external: ({importee, importer, resolved, pkg})=> pkg.builtin||(pkg&&pkg.name==='mongodb')// Flag builtins as external }, steps: (serverConfig)=>[ [ { in: "main.js", out: "main.js", bundle:"flat", }, ], ] } ], ] })); result = await builder.run(settings, { watch: watch } ); }catch(err){ error = true; console.error(err); } // Start server? if(result&&server) { console.log("Starting server..."); let started = exec("node \"./main.js\"",{ cwd: './dist/web' }); started?.stdout.on('data', (data)=>{ console.log(data.toString()); }); started?.stderr.on('data', (data)=>{ console.error(data.toString()); }); } if(watch) { // If in watch/dev-server mode, enter this crappy infinite loop (TODO?) let done = false; let interval = 500; while (!done) { let msg = await new Promise((resolve) => setTimeout(() => resolve(error ? "KILL" : "TICK"), interval)); if (msg === "KILL") { done = true; } } } return error; } // Start try { let args = process.argv.slice(2); let options = readArgs(args); build(options).catch(ex=>{ console.error(ex); }); }catch(ex){ console.error(ex); }