UNPKG

opensphere-build-resolver

Version:

Resolves projects, their dependencies, plugins, and config to the correct arguments for compilation via the Google Closure Compiler, sass/node-sass, and other tools.

285 lines (237 loc) 9.14 kB
'use strict'; const Promise = require('bluebird'); const fs = Promise.promisifyAll(require('fs')); const fse = require('fs-extra'); const glob = require('glob'); const path = require('path'); const concat = Promise.promisifyAll({ concat: require('concat-files') }); const mkdirp = require('mkdirp'); const utils = require('../../utils'); var directories = {}; var scssPaths = []; var scssEntries = []; var basePackage = null; var themes = []; var fonts = []; var scssShortcuts = { 'compass-mixins': 'compass-mixins/lib', 'bootstrap': 'bootstrap/scss', 'bootswatch': 'bootswatch/dist' }; const resolver = function(pack, projectDir, depth) { basePackage = basePackage || pack; // skip config packages if (utils.isConfigPackage(pack)) { return Promise.resolve(); } directories[pack.name] = projectDir; if (pack.dependencies) { for (var dep in pack.dependencies) { var shortcut = scssShortcuts[dep]; if (shortcut) { // try to resolve the path by walking node_modules var scssPath = utils.resolveModulePath(shortcut, projectDir); if (!scssPath) { // resolve the path from the project directory if not found in node_modules scssPath = utils.flattenPath(path.resolve(projectDir, 'node_modules/' + shortcut)); } if (scssPath) { console.log(dep + ' resolved to ' + scssPath); scssPaths.push(scssPath); } } } } if (pack.directories && pack.directories.scss) { var dirs = pack.directories.scss; dirs = typeof dirs === 'string' ? [dirs] : dirs; scssPaths = scssPaths.concat(dirs.map(function(dir) { return path.resolve(projectDir, dir); })); } if (pack.build && pack.build.scssPaths && Array.isArray(pack.build.scssPaths)) { scssPaths = scssPaths.concat(pack.build.scssPaths.map(function(dir) { var p = utils.resolveModulePath(dir, projectDir); return p ? p : path.resolve(projectDir, dir); })); } // don't include scss entries for app packages other than the root level if (pack.build && pack.build.scss && ((!utils.isAppPackage(pack) && utils.isPluginOfPackage(basePackage, pack)) || depth === 0)) { var value = pack.build.scss; if (!(value instanceof Array)) { value = [value]; } value = value.map(function(item) { return path.relative(process.cwd(), path.join(projectDir, item)); }); scssEntries = scssEntries.concat(value); } // Allow other packages we depend on to add themes if (pack.build && pack.build.themes) { themes = themes.concat(pack.build.themes); } // Allow other packages we depend on to add fonts if (pack.build && pack.build.fonts) { fonts = fonts.concat(pack.build.fonts); } return Promise.resolve(); }; const COLOR_REGEX = /colors?$/; const MIXIN_REGEX = /mixins?$/; const isMixin = function(file) { return MIXIN_REGEX.test(file); }; const isColor = function(file) { return COLOR_REGEX.test(file); }; const isRegular = function(file) { return !MIXIN_REGEX.test(file) && !COLOR_REGEX.test(file); }; const addScssRequires = function(pack, dir) { // add all requires to index.js if (pack.build && !utils.isAppPackage(pack) && !utils.isPluginPackage(pack) && pack.directories && pack.directories.scss) { var base = directories[pack.name]; var file = path.resolve(dir, 'require-all.scss'); var scssDir = path.resolve(base, pack.directories.scss); return new Promise(function(resolve, reject) { glob(path.join(scssDir, '**/*.scss'), function(err, files) { if (err) { reject(new Error(`Unable to resolve SCSS requires at ${scssDir}.`)); return; } files = files.map(function(file) { return file.replace(scssDir + path.sep, '') .replace(/_(.*)\.scss$/, '$1'); }); var colors = files.filter(isColor); var mixins = files.filter(isMixin); var rest = files.filter(isRegular); files = colors.concat(mixins).concat(rest); resolve(fs.readFileAsync(path.resolve(__dirname, 'require-all.scss'), 'utf8') .then(function(template) { console.log('Writing ' + file + ' for library scss compilation'); template += '\n@import "' + files.join('";\n@import "') + '";'; return fs.writeFileAsync(file, template); })); }); }); } return Promise.resolve(); }; const writer = function(thisPackage, outputDir) { if (scssPaths.length || scssEntries.length) { var options = {}; if ((thisPackage.build && thisPackage.build.scssCompiler === 'sass') || ((thisPackage.dependencies && thisPackage.dependencies['sass']) || (thisPackage.devDependencies && thisPackage.devDependencies['sass'])) && !((thisPackage.dependencies && thisPackage.dependencies['node-sass']) || (thisPackage.devDependencies && thisPackage.devDependencies['node-sass']))) { options['load-path'] = scssPaths; } else { options['include-path'] = scssPaths; } var args = []; for (var key in options) { var value = options[key]; for (var i = 0, n = value.length; i < n; i++) { args.push('--' + key); args.push(value[i]); } } var promises = []; var file = null; if (thisPackage.build && thisPackage.build.type !== 'app' && thisPackage.build.type !== 'plugin') { // define the input as the require-all.scss file we just wrote above args.push(path.resolve(outputDir, 'require-all.scss')); } else { file = path.resolve(outputDir, 'combined.scss'); args.push(file); console.log('Writing ' + file); promises.push(concat.concatAsync(scssEntries, file)); } file = path.resolve(outputDir, 'node-sass-args'); console.log('Writing ' + file); promises.push(fs.writeFileAsync(file, args.join(' '))); var themeDir = outputDir + '/themes'; var fontsDir = themeDir + '/fonts'; if (fonts && fonts.length) { promises.push(mkdirp(fontsDir) .then(() => { var fontPromises = []; fonts.forEach(function(font) { var fontFolder = path.resolve(fontsDir, font); fontPromises.push(mkdirp(fontFolder) .then(() => { var fontPath = utils.resolveModulePath(font); console.log('Copying ' + fontPath + ' to ' + fontFolder); var writePromises = []; writePromises.push(fse.copy(fontPath + '/files', fontFolder + '/files')); writePromises.push(fs.copyFileAsync(fontPath + '/index.css', fontFolder + '/index.css')); return Promise.all(writePromises); })); }); return Promise.all(fontPromises); })); } return Promise.all(promises) .then(function() { // Write a file for each theme also if (utils.isAppPackage(thisPackage) && thisPackage.build.scss && themes && themes.length) { // Take off the combined.scss generic file args.pop(); var themeDir = outputDir + '/themes'; return mkdirp(themeDir) .then(function() { var promises = []; themes.push('default'); // For all the themes, create a combined.scss and a node-sass-args file themes.forEach(function(theme) { var themeOutput = path.resolve(themeDir, theme + '.combined.scss'); var themeArgs = path.resolve(themeDir, theme + '.node-sass-args'); promises.push(fs.writeFileAsync(themeArgs, args.join(' ') + ' ' + themeOutput)); promises.push(fs.readFileAsync(path.resolve(outputDir, 'combined.scss'), 'utf8') .then(function(fileContents) { console.log('Creating combined.scss for ' + theme + ' theme'); // Add the theme name to a class for stylesheet load detection. fileContents = '.u-loaded-theme::before { content: "' + theme + '"; }\n' + fileContents; // Prepend and Append our theme around the bootstrap entry var bootstrapEntry = '@import \'bootstrap\';'; if (theme != 'default') { fileContents = fileContents.replace(bootstrapEntry, '@import \'' + theme + '/_variables\';' + '\n' + bootstrapEntry + '\n' + '@import \'' + theme + '/_bootswatch\';'); } return fs.writeFileAsync(themeOutput, fileContents); })); }); return Promise.all(promises); }); } else { return Promise.resolve(); } }); } return Promise.resolve(); }; const clear = function() { directories = {}; scssPaths = []; scssEntries = []; fonts = []; themes = []; }; module.exports = { clear: clear, resolver: resolver, postResolver: addScssRequires, writer: writer };