UNPKG

ng-afelio

Version:
419 lines (366 loc) 18.4 kB
const { Bundler } = require('scss-bundle'); const fs = require('fs'); // const fse = require('fs-extra'); const decomment = require('decomment'); const colors = require('colors'); const sass = require('sass'); const magicImporter = require('node-sass-magic-importer'); const cssNodeExtract = require('css-node-extract'); const postcssScssSyntax = require('postcss-scss'); const path = require('path'); const findParentDir = require('find-parent-dir'); const baseConfig = require('../templates/config/ng-afelio.json'); const configPath = 'ng-afelio.json'; let outputDirectory = '../../styles'; let inputDirectory = './projects/ui-kit'; let inputPrefix = './src/'; let angularConfigPath = '../../angular.json'; function resolveToNodeModules(targetUrl, currentPath, defaultIndex = 'index') { var packageRoot = findParentDir.sync(currentPath, 'node_modules'); if (!packageRoot) { return null; } var filePath = path.resolve(packageRoot, 'node_modules', targetUrl); var isPotentiallyDirectory = !path.extname(filePath); if (isPotentiallyDirectory) { if (fs.existsSync(filePath + '.scss')) { return filePath + '.scss'; } if (fs.existsSync(filePath)) { return path.resolve(filePath, defaultIndex); } } if (fs.existsSync(path.dirname(filePath))) { return filePath; } return resolveToNodeModules(targetUrl, path.dirname(packageRoot)); } const tildeImporter = function (url, currentPath) { return (url[ 0 ] === '~') ? { file: resolveToNodeModules(url.substr(1), currentPath) } : null; }; function reorganiseScssUse(scssSrc) { let toReplace = []; scssSrc = scssSrc.replace(/@use .+;/gi, replacer => { if (!toReplace.includes(replacer)) { toReplace.push(replacer) } return ''; }); const usesLine = toReplace.join(' '); return usesLine + scssSrc; } function logSameLine(textToLog, isEnd) { process.stdout.clearLine(0); process.stdout.cursorTo(0); process.stdout.write(textToLog); if (isEnd) { process.stdout.write("\n"); } } function enforceDirectoryExistance(currentPath) { const directory = path.dirname(currentPath); if (!fs.existsSync(directory)) { fs.mkdirSync(directory); } } function readConfig() { if (!fs.existsSync(configPath)) { writeConfig(baseConfig); } const fileContent = fs.readFileSync(configPath, 'utf8'); return JSON.parse(fileContent); } function writeConfig(jsonContent) { fs.writeFileSync(configPath, JSON.stringify(jsonContent, null, 2), 'utf8'); } function initFolder(config) { inputDirectory = typeof config.style.baseInputDirectory !== 'undefined' ? config.style.baseInputDirectory : inputDirectory; process.chdir(inputDirectory); outputDirectory = typeof config.style.baseOutputDirectory !== 'undefined' ? config.style.baseOutputDirectory : outputDirectory; if (!fs.existsSync(outputDirectory)) { fs.mkdirSync(outputDirectory); } inputPrefix = typeof config.style.inputPrefix !== 'undefined' ? config.style.inputPrefix : inputPrefix; // Angular config const countSlashes = inputDirectory.replace('./', '').split('/').reduce((result) => { return result += '../'; }, ''); angularConfigPath = `${countSlashes}angular.json`; } async function asyncForEach(array, callback) { for (let index = 0; index < array.length; index++) { await callback(array[index], index, array); } } async function buildUtils(config, bundlerBasePath) { const bundler = new Bundler(undefined, bundlerBasePath); if (!config.style.styleUtils) { return false; } const isNewArrayVersion = Array.isArray(config.style.styleUtils); function bundling(input, output) { return bundler.bundle(path.join(inputPrefix, input)).then(result => { if (!result.found) { console.error(colors.red(`BUILD ERROR file "${input}" not found`)); return Promise.resolve(false); } const toWrite = decomment.text(result.bundledContent/*, {safe: true}*/); const options = { css: toWrite, filters: ['silent'], postcssSyntax: postcssScssSyntax }; return cssNodeExtract.process(options).then((extractedCss) => { const outputPath = path.join(outputDirectory, output); enforceDirectoryExistance(outputPath); return new Promise((resolve, error) => { extractedCss = '@use "sass:math";\n' + extractedCss; fs.writeFile(outputPath, extractedCss, function (err) { if (!err) { console.info(`${colors.green('BUILD style utils')} was saved in "${outputPath}"`); resolve(true); } else { console.error(colors.red(`WRITE FILE ERROR ${output}`), err); error(err); } }); }); }, err => { console.error(colors.red(`BUILD ERROR ${output}`), err); }); }); } if (isNewArrayVersion) { // New Version const enabled = config.style.styleUtils.length > 0; if (enabled) { let allGenerated = true; await asyncForEach(config.style.styleUtils, async styleUtilConfig => { allGenerated = allGenerated && await bundling(styleUtilConfig.input, styleUtilConfig.output); }); return allGenerated; } else { return false; } } else { // Old Version const enabled = config.style.styleUtils.enable; if (enabled) { const input = (config && config.style && config.style.styleUtils && config.style.styleUtils.input) || 'styles.scss'; const output = (config && config.style && config.style.styleUtils && config.style.styleUtils.output) || 'style-utils.scss'; return await bundling(input, output); } else { return false; } } } function buildStyleFiles(config, bundlerBasePath) { const bundler = new Bundler(undefined, bundlerBasePath); const files = (config && config.style && config.style.files) || [{ "input": "styles.scss", "output": "main.scss" }]; function bundling(input, output, isGlobal) { logSameLine(`${colors.blue(`PROCESS bundling...`)} "${input}"`); const mainFile = path.join(process.cwd(), path.join(inputPrefix, input)); const mainFolder = path.dirname(mainFile); return bundler.bundle(path.join(inputPrefix, input)).then(async result => { if (!result.found) { console.error(colors.red(`BUILD ERROR file "${input}" not found`)); return Promise.resolve(false); } logSameLine(`${colors.blue(`PROCESS uncommenting...`)} "${input}"`); let toWrite = decomment.text(result.bundledContent/*, {safe: true}*/); logSameLine(`${colors.blue(`PROCESS reorganising...`)} "${input}"`); toWrite = reorganiseScssUse(toWrite); logSameLine(`${colors.blue(`PROCESS compiling...`)} "${input}"`, true); const outputPath = path.join(outputDirectory, output); enforceDirectoryExistance(outputPath); // const cwd = process.cwd(); // process.chdir(path.dirname(path.join(process.cwd(), inputPrefix, input))); await new Promise(async (resolve, error) => { try { const nodeModulesPath = resolveToNodeModules('', process.cwd(), ''); const compiledFile = sass.compileString(toWrite, { importers: [ // Magic-importer { canonicalize: (url, options) => { if (!options.fromImport) { return null; } let decodedUrl = decodeURIComponent(url); if (decodedUrl.startsWith('file://')) { if (!decodedUrl.includes('{') && !decodedUrl.includes('[')) { return new URL(url); } console.log(colors.bgYellow(`magic-importer is deprecated. Don't use it in new projects`)); decodedUrl = decodedUrl.substring(7); // Remove file:// const startIndex = decodedUrl.includes('{') ? decodedUrl.indexOf('{') : decodedUrl.indexOf('['); const endIndex = decodedUrl.indexOf('from') + 4; const beforeString = decodedUrl.substring(startIndex, endIndex); decodedUrl = `${beforeString} ${decodedUrl.replace(beforeString, '').replace(/ /g, '')}`; } if (decodedUrl.includes('{') || decodedUrl.includes('[')) { console.log(colors.bgYellow(`magic-importer is deprecated. Don't use it in new projects`)); const newUrl = new URL('file://' + decodedUrl.substring(decodedUrl.indexOf('from') + 4).trim()); newUrl.searchParams.append('decodedUrl', decodedUrl); return newUrl; } else { return null; } }, load: function (url) { const decodedUrl = url.searchParams.get('decodedUrl'); if (!decodedUrl) { return { contents: fs.readFileSync(path.join(mainFolder, url.hostname, url.pathname)).toString(), syntax: 'scss' }; } // else { // return { // contents: fs.readFileSync(path.join(mainFolder, decodedUrl.substring(decodedUrl.indexOf('from') + 4).trim())).toString(), // syntax: 'scss' // }; // } // const cleanedUrl = decodedUrl.substring(decodedUrl.indexOf('from') + 4).trim(); const importer = magicImporter(); try { const magicImport = importer.bind({ options: { cwd: process.cwd(), extensions: [ `.scss`, `.sass`, `.css`, ], packageKeys: [ `sass`, `scss`, `style`, `css`, `main.sass`, `main.scss`, `main.style`, `main.css`, `main`, ], packagePrefix: `~`, disableImportOnce: false, customFilters: undefined, includePaths: '' } })(decodedUrl, mainFile); return { contents: magicImport.contents, syntax: 'scss' }; } catch (e) { console.log(colors.yellow(`Can not find (${decodedUrl}) from magic-importer. You must use magic-importer from root import to be able to use it here.`)); return null; } } }, // Tilde importer { findFileUrl: function (url, options) { const tildeImporterResult = tildeImporter(url, process.cwd()); const tildeImporterResultFile = tildeImporterResult ? tildeImporterResult.file : null; return tildeImporterResultFile ? new URL( 'file://' + tildeImporterResultFile) : null; } } ], quietDeps: true, style: 'expanded', verbose: true, loadPaths: [ nodeModulesPath ] }); fs.writeFile(outputPath, compiledFile.css, (err) => { if (!err) { console.info(`${colors.green(`BUILD ${isGlobal ? 'global ' : ''}style`)} was saved in "${outputPath}"`); resolve(); } else { console.error(colors.red(`WRITE FILE ERROR ${outputPath}`), err); error(err); } }); } catch (err) { console.error(colors.red(`BUILD ERROR ${outputPath}`), err); error(err); } }); if (isGlobal) { const toAddToStyleScss = `@import '../styles/${output}';`; const stylePath = '../../src/styles.scss'; const fileContent = fs.readFileSync(stylePath, 'utf8'); if (!fileContent.includes(toAddToStyleScss)) { fs.appendFileSync(stylePath, `${toAddToStyleScss}`); console.info(`${colors.green('ADDED')} "${toAddToStyleScss}" added into "/src/styles.scss"`); } } }); } return asyncForEach(files, async file => { const input = file.input; const output = file.output || file.input; const isGlobal = file.global; await bundling(input, output, isGlobal); }) } function checkAssetsConfiguration(config) { if (!config || !config.style || config.style.addUiKitAssets !== false) { const assetsPath = `${inputDirectory.replace('./', '')}/src/assets`; const toAddToAngularConfig = { "input": assetsPath, "glob": "**/*", "output": "./assets" }; const fileContent = fs.readFileSync(angularConfigPath, 'utf8'); if (!fileContent.includes(`"input": "${assetsPath}"`)) { const jsonContent = JSON.parse(fileContent); const defaultProject = jsonContent.defaultProject; const builds = Object.keys(jsonContent.projects[defaultProject].architect); builds.forEach((build) => { const currentBuildConfig = jsonContent.projects[defaultProject].architect[build]; if (currentBuildConfig.options && currentBuildConfig.options.assets) { currentBuildConfig.options.assets.push(toAddToAngularConfig); } }); fs.writeFileSync(angularConfigPath, JSON.stringify(jsonContent, null, 2), 'utf8'); console.info(`${colors.green('ADD ASSETS')} UI Kit assets added to ${defaultProject} project`); } } } function checkStyleShortcut(config) { if (!config || !config.style || config.style.addAngularStyleShortcut !== false) { const stylePath = path.join(inputDirectory, outputDirectory); const fileContent = fs.readFileSync(angularConfigPath, 'utf8'); if (!fileContent.includes('"stylePreprocessorOptions"')) { const jsonContent = JSON.parse(fileContent); const defaultProject = jsonContent.defaultProject; const buildOptions = jsonContent.projects[defaultProject].architect.build.options; buildOptions.stylePreprocessorOptions = { includePaths: [stylePath] }; fs.writeFileSync(angularConfigPath, JSON.stringify(jsonContent, null, 2), 'utf8'); console.info(`${colors.green('ADD CSS SHORTCUT')} "styles" shortcut added to ${defaultProject} project`); } } } let currentConfig; function buildStyleFromUIKit(needInit = true) { if (needInit) { currentConfig = readConfig(); initFolder(currentConfig); } const currentPath = process.cwd(); return Promise.all([ buildUtils(currentConfig, currentPath), buildStyleFiles(currentConfig, currentPath) ]).then(() => { if (needInit) { checkAssetsConfiguration(currentConfig); checkStyleShortcut(currentConfig); } console.info(`${colors.green('STYLE BUILT')} All styles was build successfuly.`); }, () => { console.info(`${colors.red('STYLE BUILT')} Some styles get errors on build.`); }); } module.exports = { buildStyleFromUIKit };