├── .nvmrc ├── config ├── stats.config.js ├── devserver.config.js ├── cache.config.js ├── postcss.config.js ├── performance.config.js ├── eslint.config.js ├── stylelint.config.js ├── output.config.js ├── resolve.config.js ├── babel.config.js ├── templates.config.js ├── sass.config.js ├── clientlibs.config.js ├── plugins.config.js ├── index.js ├── optimization.config.js └── general.config.js ├── cli └── index.js ├── test ├── src │ ├── common │ │ ├── styles │ │ │ ├── _variables.scss │ │ │ └── _media-queries.scss │ │ └── utils │ │ │ ├── random.js │ │ │ ├── randomInt.js │ │ │ └── math.js │ ├── author │ │ ├── author.src.scss │ │ └── author.src.js │ ├── publish │ │ ├── component │ │ │ └── component.src.scss │ │ ├── publish.src.scss │ │ └── publish.src.js │ └── minimal │ │ ├── .febuild │ │ └── .febuild.js ├── .eslintrc.json ├── index.html ├── stylelint.config.js ├── .febuild └── .febuild.js ├── docs ├── images │ └── task-webpack.png ├── tasks.md ├── CONTRIBUTING.md ├── quick_start.md ├── CODE_OF_CONDUCT.md ├── migration.md ├── configuration.md └── CHANGELOG.md ├── .npmignore ├── .editorconfig ├── utils ├── linterError.js ├── getClientlib.js ├── mkFullPathSync.js ├── getArgumentValue.js ├── linterError.test.js ├── checkChunk.js ├── log.test.js ├── writeFile.js ├── getPlugins.js ├── mkFullPathSync.test.js ├── renderStyles.js ├── merge.js ├── getClientlib.test.js ├── generateEntries.js ├── checkChunk.test.js ├── runStylelint.js ├── getArgumentValue.test.js ├── merge.test.js ├── taskVerification.js ├── extendConfig.js ├── renderPostcss.js ├── renderClientLibs.js ├── getPlugins.test.js ├── generateEntries.test.js ├── extendConfig.test.js ├── log.js ├── renderSass.js └── renderPostcss.test.js ├── .releaserc ├── .github ├── ISSUE_TEMPLATE.md ├── workflows │ ├── ci.yml │ ├── release.yml │ ├── codeql-analysis.yml │ └── manual-release.yml └── PULL_REQUEST_TEMPLATE.md ├── hooks └── pre-commit ├── SECURITY.md ├── .gitignore ├── tasks ├── styles.test.js ├── clientlibs.js ├── webpack.test.js ├── run.js ├── clientlibs.test.js ├── styles.js └── webpack.js ├── package.json ├── README.md └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 2 | -------------------------------------------------------------------------------- /config/stats.config.js: -------------------------------------------------------------------------------- 1 | module.exports = 'verbose'; -------------------------------------------------------------------------------- /config/devserver.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | } -------------------------------------------------------------------------------- /cli/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../tasks/run'); 3 | -------------------------------------------------------------------------------- /config/cache.config.js: -------------------------------------------------------------------------------- 1 | // webpack cache 2 | module.exports = true -------------------------------------------------------------------------------- /test/src/common/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #5a3699; 2 | $secondary-color: #fff; 3 | -------------------------------------------------------------------------------- /config/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['autoprefixer'], 3 | failOnError: true 4 | }; 5 | -------------------------------------------------------------------------------- /docs/images/task-webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netcentric/fe-build/HEAD/docs/images/task-webpack.png -------------------------------------------------------------------------------- /config/performance.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | maxAssetSize: 100000, 3 | maxEntrypointSize: 350000, 4 | }; -------------------------------------------------------------------------------- /config/eslint.config.js: -------------------------------------------------------------------------------- 1 | // eslint plugin configuration 2 | module.exports = { 3 | failOnError: true, 4 | fix: true 5 | }; -------------------------------------------------------------------------------- /config/stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Stops build if a linter error is found 3 | failOnError: true, 4 | }; 5 | -------------------------------------------------------------------------------- /test/src/common/utils/random.js: -------------------------------------------------------------------------------- 1 | export function random(min = 0, max = 1) { 2 | return Math.random() * (max - min) + min; 3 | } -------------------------------------------------------------------------------- /test/src/common/utils/randomInt.js: -------------------------------------------------------------------------------- 1 | 2 | export function randomInt(min = 0, max = 1) { 3 | return Math.round(Math.random() * (max - min) + min); 4 | } 5 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "browser": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 2020, 8 | "sourceType": "module" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/src/common/utils/math.js: -------------------------------------------------------------------------------- 1 | export function randomInt(min = 0, max = 1) { 2 | return Math.round(random(min, max)); 3 | } 4 | 5 | export function random(min = 0, max = 1) { 6 | return Math.random() * (max - min) + min; 7 | } 8 | -------------------------------------------------------------------------------- /config/output.config.js: -------------------------------------------------------------------------------- 1 | const path = require('./general.config').destinationPath; 2 | 3 | module.exports = { 4 | path, 5 | filename: '[name]', 6 | chunkFilename: '[name]', 7 | publicPath: '/', 8 | pathinfo: false 9 | }; 10 | -------------------------------------------------------------------------------- /config/resolve.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { 4 | common, 5 | nodeModules 6 | } = require('./general.config'); 7 | 8 | module.exports = { 9 | alias: { 10 | utils: path.join(common, 'utils'), 11 | }, 12 | modules: [nodeModules] 13 | }; 14 | -------------------------------------------------------------------------------- /test/src/author/author.src.scss: -------------------------------------------------------------------------------- 1 | @use 'media-queries' as mq; 2 | 3 | body { 4 | @include mq.for-size(s) { 5 | font-size: 1.5em; 6 | 7 | 8 | 9 | } 10 | 11 | @include mq.for-size(m) { 12 | font-size: 1em; 13 | } 14 | } 15 | 16 | ::placeholder { 17 | color: gray; 18 | } -------------------------------------------------------------------------------- /test/src/publish/component/component.src.scss: -------------------------------------------------------------------------------- 1 | ::placeholder { 2 | color: gray; 3 | } 4 | 5 | .image { 6 | background-image: url("image@1x.png"); 7 | } 8 | 9 | @media (resolution >= 2dppx) { 10 | .image { 11 | background-image: url("image@2x.png"); 12 | } 13 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .eslintrc 2 | .nyc_output 3 | rollup.config.js 4 | .prettierrc 5 | .git 6 | .prettierignore 7 | **/.git/ 8 | **/node_modules/ 9 | yarn.lock 10 | /dev 11 | /docs 12 | /public 13 | /coverage 14 | /.vscode 15 | /tests 16 | /temp 17 | /templates 18 | /demos 19 | /src 20 | .github 21 | /test 22 | -------------------------------------------------------------------------------- /test/src/publish/publish.src.scss: -------------------------------------------------------------------------------- 1 | @use 'variables'; 2 | 3 | body { 4 | color: variables.$secondary-color; 5 | background-color: variables.$primary-color; 6 | 7 | @media (min-resolution: 2dppx) { 8 | .image { 9 | background-image: url('image@2x.png'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /test/src/minimal/.febuild: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | babel: { 5 | use: { 6 | options: { 7 | presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3, debug: true }]] 8 | } 9 | } 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /test/src/minimal/.febuild.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | babel: { 5 | use: { 6 | options: { 7 | presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3, debug: true }]] 8 | } 9 | } 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /config/babel.config.js: -------------------------------------------------------------------------------- 1 | // Webpack configuration 2 | module.exports = { 3 | enforce: 'post', 4 | test: /\.js$/, 5 | exclude: /node_modules\/(?!@netcentric)/, 6 | use: { 7 | loader: 'babel-loader', 8 | options: { 9 | presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }]], 10 | plugins: ['@babel/plugin-transform-runtime'] 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /utils/linterError.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { log, color } = require('./log'); 3 | 4 | module.exports = function linterError(lintError, fileError) { 5 | const location = `${(fileError.source)}:${lintError.line}:${lintError.column} \n`; 6 | log(__filename, ` 7 | ${lintError.text}${color('reset', '')} 8 | ${color('dim', `LintError at ${location}`)}`, '', 'error', true); 9 | }; 10 | -------------------------------------------------------------------------------- /config/templates.config.js: -------------------------------------------------------------------------------- 1 | 2 | const clientlibTemplate = (category, prefix) => ` 3 | 7 | `; 8 | 9 | module.exports = { 10 | clientlibTemplate 11 | }; 12 | -------------------------------------------------------------------------------- /utils/getClientlib.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function getClientlib(original) { 4 | 5 | const folder = path.dirname(original); 6 | const fileName = path.basename(original); 7 | const name = folder.split(path.sep).join('.'); 8 | const extension = original.split('.').pop(); 9 | 10 | return { 11 | folder, 12 | name, 13 | fileName, 14 | extension, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /utils/mkFullPathSync.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = function mkFullPathSync(absolutePath, permissions = '0755') { 5 | // if is not set to not override 6 | absolutePath.split(path.sep).reduce((origin, folder) => { 7 | const next = `${origin}${folder}${path.sep}`; 8 | if (!fs.existsSync(next)) fs.mkdirSync(next, permissions); 9 | return next; 10 | }, ''); 11 | }; 12 | -------------------------------------------------------------------------------- /test/src/author/author.src.js: -------------------------------------------------------------------------------- 1 | import { randomInt } from "common/utils/randomInt"; 2 | import { random } from "common/utils/random"; 3 | 4 | class MainAuthor { 5 | constructor() { 6 | this.value = randomInt(1, 10); 7 | this.value2 = random(1, 10); 8 | this.init(); 9 | } 10 | 11 | init() { 12 | console.log(`Hello Author ${this.value} author ${this.value2}!`); 13 | } 14 | } 15 | 16 | export const instace = new MainAuthor(); 17 | -------------------------------------------------------------------------------- /config/sass.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { isProduction, common, nodeModules } = require('./general.config'); 3 | 4 | const includePaths = [path.join(common, 'styles'), nodeModules]; 5 | const outputStyle = isProduction ? 'compressed' : 'expanded'; 6 | 7 | module.exports = { 8 | includePaths, 9 | outputStyle, 10 | // for any https://sass-lang.com/documentation/js-api/interfaces/options/ options 11 | adicionalOptions: {} 12 | }; 13 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/src/common/styles/_media-queries.scss: -------------------------------------------------------------------------------- 1 | @mixin for-size($size) { 2 | @if $size == s { 3 | @media (min-width: 600px) { 4 | @content; 5 | } 6 | } @else if $size == m { 7 | @media (min-width: 768px) { 8 | @content; 9 | } 10 | } @else if $size == l { 11 | @media (min-width: 960px) { 12 | @content; 13 | } 14 | } @else if $size == xl { 15 | @media (min-width: 1200px) { 16 | @content; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/src/publish/publish.src.js: -------------------------------------------------------------------------------- 1 | import { randomInt } from "common/utils/randomInt"; 2 | 3 | class MainPublish { 4 | constructor() { 5 | this.value = randomInt(1, 10); 6 | this.arr = [...[0, 1, 2]]; 7 | this.obj = { 8 | name: 'test' 9 | }; 10 | this.init(); 11 | } 12 | 13 | init() { 14 | const { name } = this.obj; 15 | console.log(`Hello World ${this.value}! ${name}`); 16 | } 17 | } 18 | export const instace = new MainPublish(); 19 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main"], 3 | "plugins": [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | [ 7 | "@semantic-release/changelog", 8 | { 9 | "changelogFile": "docs/CHANGELOG.md" 10 | } 11 | ], 12 | "@semantic-release/npm", 13 | [ 14 | "@semantic-release/git", 15 | { 16 | "assets": ["docs/CHANGELOG.md", "package.json"] 17 | } 18 | ] 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Expected Behaviour 4 | 5 | ### Actual Behaviour 6 | 7 | ### Reproduce Scenario (including but not limited to) 8 | 9 | #### Steps to Reproduce 10 | 11 | #### Platform and Version 12 | 13 | #### Sample Code that illustrates the problem 14 | 15 | #### Logs taken while reproducing problem 16 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "***** ******" 3 | echo "***** Cognizant Netcentric (https://www.netcentric.biz) ******" 4 | echo "***** ******" 5 | echo "***** Running jest test :) ******" 6 | echo "***** ******" 7 | 8 | git stash -q --keep-index 9 | 10 | npm test 11 | 12 | status=$? 13 | 14 | git stash pop -q 15 | 16 | exit $status -------------------------------------------------------------------------------- /utils/getArgumentValue.js: -------------------------------------------------------------------------------- 1 | module.exports = (key) => { 2 | if (process.argv) { 3 | // get the last argument of the key 4 | const args = Array.from(process.argv) 5 | .filter(arg => arg.indexOf(key) >= 0).pop(); 6 | 7 | if (args) { 8 | // send the value if there is a value pass 9 | if (args.length !== key.length) { 10 | return args.split(key).pop(); 11 | } 12 | 13 | // return true if there is no value just the argument 14 | return true; 15 | } 16 | 17 | return false; 18 | } 19 | 20 | return false; 21 | }; 22 | -------------------------------------------------------------------------------- /utils/linterError.test.js: -------------------------------------------------------------------------------- 1 | const linterError = require("./linterError"); 2 | const path = require('path'); 3 | process.argv.push('--quiet'); 4 | 5 | describe('Test utils/linterError.js', () => { 6 | it('Check formating of a error send to console log', () => { 7 | const source = __filename; 8 | const line = 12; 9 | const column = 10; 10 | const text = 'my error' 11 | console.error = jest.fn(); 12 | linterError({line, column, text}, {source}); 13 | expect(console.error.mock.calls[0][0]).toMatchSnapshot(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Here below there is the list of versions of the fe-build which are currently being supported with security updates. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 2.0.x | :white_check_mark: | 10 | | < 2.0.0 | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | To report a vulnerability please send us an email with the details to opensource@netcentric.biz. 15 | This will help us to assess the risk and start the necessary steps. 16 | 17 | Please **do not** file a GitHub issue for a vulnerability. 18 | -------------------------------------------------------------------------------- /test/stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['stylelint-config-standard-scss'], 3 | ignoreFiles: [ 4 | '**/*.css', 5 | '**/*.js', 6 | '**/node_modules/**' 7 | ], 8 | rules: { 9 | 'selector-type-no-unknown': [true, { 10 | ignore: [ 11 | 'custom-elements', 12 | 'default-namespace' 13 | ] 14 | }], 15 | 'media-feature-range-notation': null, 16 | 'at-rule-no-unknown': [true, { 17 | ignoreAtRules: ['use', 'function', 'if', 'each', 'else', 'for', 'include', 'mixin', 'return', 'warn'] 18 | }] 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /utils/checkChunk.js: -------------------------------------------------------------------------------- 1 | const includesInModules = (names = []) => (module) => names.filter((name) => module.includes(name)).length > 0; 2 | 3 | // To check if a context is from a vendor 4 | module.exports = function checkChunk(module, excludes = [], includes = []) { 5 | // is not a external module so there is no context 6 | if (!module) { 7 | return false; 8 | } 9 | 10 | if (includesInModules(excludes)(module)) { 11 | return false; 12 | } 13 | // has includes defined and the module is not in the includes 14 | if (includes.length === 0 ) { 15 | return true; 16 | } 17 | 18 | return includesInModules(includes)(module); 19 | }; 20 | -------------------------------------------------------------------------------- /config/clientlibs.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | clientLibs generation configuration 4 | All other configurations 5 | 6 | */ 7 | 8 | // Override all clientlibs 9 | const override = false; 10 | 11 | // array of categories to skip (if override is true) 12 | const skipCategories = ['myproject.author']; 13 | // extra XML params to append to .content.xml use key=value 14 | // linter disabled since we are requirement to send $\{ to a template string 15 | 16 | // Object to be able to generate clientlibs for new CSS files (created in build process) in target folder 17 | const extraEntries = {}; 18 | 19 | module.exports = { 20 | override, 21 | skipCategories, 22 | extraEntries, 23 | }; 24 | -------------------------------------------------------------------------------- /config/plugins.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const { mode, analyze, analyzerPort } = require('./general.config'); 3 | 4 | // pass the mode forward 5 | const environment = new webpack.DefinePlugin({ 6 | 'process.env.NODE_ENV': JSON.stringify(mode) 7 | }); 8 | 9 | // default plugins 10 | const plugins = [environment]; 11 | 12 | // check each files / dependencies sizes 13 | // src https://www.npmjs.com/package/webpack-bundle-analyzer 14 | if (analyze) { 15 | const BundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 16 | const analyzer = new BundleAnalyzer({ 17 | analyzerPort 18 | }); 19 | 20 | plugins.push(analyzer); 21 | } 22 | 23 | module.exports = plugins; 24 | -------------------------------------------------------------------------------- /utils/log.test.js: -------------------------------------------------------------------------------- 1 | const { severity, log } = require("./log"); 2 | 3 | describe('Test utils/log.js', () => { 4 | Object.keys(severity).forEach((severetyName,index) => { 5 | it(`Check formating of each error severety send to console log [${severetyName}]`, () => { 6 | const fn = severetyName === 'error' ? severetyName : 'log'; 7 | const file = __filename; 8 | const title = 'My error'; 9 | const extra = 'Extra error information'; 10 | const text = 'my error'; 11 | console[fn] = jest.fn(); 12 | log(file, title, extra, severetyName); 13 | expect(console[fn].mock.calls[0][0]).toMatchSnapshot(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /utils/writeFile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { log } = require('./log'); 3 | const path = require('path'); 4 | 5 | module.exports = function writeFile(filepath, content, override = false) { 6 | const normalizePath = path.normalize(filepath); 7 | // if is not set to not override 8 | if (!override && fs.existsSync(normalizePath)) return; 9 | const dir = path.dirname(normalizePath); 10 | if (!fs.existsSync(dir)){ 11 | fs.mkdirSync(dir, { recursive: true }); 12 | } 13 | // white it 14 | fs.writeFileSync(normalizePath, content, 15 | err => log(__filename, 'error', `could not write file: ${err.Error},'\n ${err.path}`, 'error')); 16 | 17 | log(__filename, `write file...${normalizePath}`, '', 'info'); 18 | }; 19 | -------------------------------------------------------------------------------- /utils/getPlugins.js: -------------------------------------------------------------------------------- 1 | module.exports = function getPlugins(plugins = []) { 2 | return plugins 3 | .filter(Boolean) 4 | .map(function (entry) { 5 | if (Array.isArray(entry)) { 6 | const [pluginName, pluginOptions] = entry; 7 | const pluginModule = require(pluginName); 8 | const pluginFn = pluginModule.default || pluginModule; 9 | return pluginFn(pluginOptions); 10 | } else if (typeof entry === 'string') { 11 | const pluginModule = require(entry); 12 | return pluginModule.default || pluginModule; 13 | } else { 14 | throw new Error(`Invalid plugin entry: ${JSON.stringify(entry)}`); 15 | } 16 | }); 17 | }; -------------------------------------------------------------------------------- /utils/mkFullPathSync.test.js: -------------------------------------------------------------------------------- 1 | process.argv.push('--quiet'); 2 | const mkdirSync = require("./mkFullPathSync"); 3 | const fs = require('fs'); 4 | 5 | const folderPath = './dist/mkFullPathSync/mkFullPathSyncExample' 6 | 7 | 8 | describe('Test utils/mkFullPathSync.js', () => { 9 | it(`It should check and create a recursive folder even if it does not exists`, () => { 10 | return new Promise((resolve) => { 11 | const removed = fs.rmSync(folderPath,{ recursive: true, force: true }); 12 | const dir = mkdirSync(folderPath); 13 | resolve(folderPath); 14 | }).then(() => { 15 | expect(fs.existsSync(folderPath)).toBe(true); 16 | const removed = fs.rmSync('./dist',{ recursive: true, force: true }); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /utils/renderStyles.js: -------------------------------------------------------------------------------- 1 | const renderSass = require('../utils/renderSass'); 2 | const renderPostcss = require('../utils/renderPostcss'); 3 | const runStylelint = require('../utils/runStylelint'); 4 | const writeFile = require('../utils/writeFile'); 5 | 6 | module.exports = async function renderStyles(file, dest, config) { 7 | return new Promise((resolve) => { 8 | runStylelint( 9 | [file], config, () => 10 | renderSass( 11 | dest, file, config, (result, outFile) => 12 | renderPostcss( 13 | result, outFile, config, postCssResult => { 14 | // only write file at the end 15 | writeFile(outFile, postCssResult.css, true); 16 | resolve(); 17 | } 18 | ) 19 | ) 20 | ); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # IDE 6 | .project 7 | .classpath 8 | .settings 9 | .idea/ 10 | *.iml* 11 | *.ipr 12 | .brackets.json 13 | .vscode/ 14 | .history 15 | 16 | # Maven 17 | target/ 18 | 19 | # File Vault (VLT) / Sync 20 | .vlt 21 | .vlt-sync* 22 | 23 | # Mac OS X 24 | .DS_Store 25 | *.swp 26 | 27 | # git 28 | *.orig 29 | 30 | # Node related files 31 | node_modules/ 32 | node/ 33 | bin/ 34 | typings 35 | npm-debug.log* 36 | yarn-debug.log* 37 | yarn-error.log* 38 | .stylelintcache 39 | .eslintcache 40 | 41 | # env 42 | \.env 43 | 44 | # bundle css and js files and maps 45 | *.bundle.* 46 | *.chunk.* 47 | symbols.svg 48 | .tmp/ 49 | .cache-loader 50 | dist/ 51 | 52 | # Svg Icons Temp folder 53 | tmp/ 54 | coverage/ 55 | 56 | # coverage folder 57 | coverage/ 58 | 59 | # Bundle Analyser 60 | frontend/stats.json 61 | __snapshots__/ 62 | -------------------------------------------------------------------------------- /utils/merge.js: -------------------------------------------------------------------------------- 1 | // fast simplified version of a merge deep 2 | // only merge object properties not values / arrays. 3 | module.exports = function merge(original = {}, newObject) { 4 | const copy = Object.assign({}, original); 5 | const keys = Object.keys(newObject); 6 | 7 | keys.forEach((prop) => { 8 | if (newObject[prop] 9 | && typeof newObject[prop] === 'object' 10 | && !Array.isArray(newObject[prop]) 11 | && !(newObject[prop] instanceof RegExp)) { 12 | copy[prop] = merge(original[prop], newObject[prop]); 13 | } else if (Array.isArray(newObject[prop])) { 14 | copy[prop] = newObject[prop] ? [...newObject[prop]] : [...original[prop]]; 15 | } else { 16 | copy[prop] = newObject[prop] || typeof newObject[prop] === 'boolean' ? newObject[prop] : original[prop]; 17 | } 18 | }); 19 | 20 | return copy; 21 | }; 22 | -------------------------------------------------------------------------------- /utils/getClientlib.test.js: -------------------------------------------------------------------------------- 1 | process.argv.push('--quiet'); 2 | const path = require('path'); 3 | const getClientlib = require("./getClientlib"); 4 | 5 | describe('Test utils/getClientlib.js', () => { 6 | 7 | it('Should return from a file location the required info for a clientlib, eg category name, file to import into js.txt etc', () => { 8 | const fileName = 'component.bundle.js'; 9 | const folder = 'publish/content/component'; 10 | const extension = 'js'; 11 | const location = `${folder}${path.sep}${fileName}`; 12 | const clientlibNameCategory = 'publish.content.component'; 13 | const clientlib = getClientlib(location); 14 | expect(clientlib.name).toBe(clientlibNameCategory); 15 | expect(clientlib.fileName).toBe(fileName); 16 | expect(clientlib.folder).toBe(folder); 17 | expect(clientlib.extension).toBe(extension); 18 | }); 19 | }) 20 | -------------------------------------------------------------------------------- /utils/generateEntries.js: -------------------------------------------------------------------------------- 1 | const glob = require('fast-glob'); 2 | const path = require('path'); 3 | 4 | module.exports = function generateEntries(config, extension = 'js', filenamePattern = config.general.sourceKey, cwd = config.general.sourcesPath) { 5 | const sourcePattern = `**/*.${filenamePattern}.${extension}`; 6 | const sourcesFiles = glob.sync(sourcePattern, { cwd: cwd }); 7 | 8 | // if is multiple entries 9 | if (config && config.general && config.general.multiple) { 10 | const sources = {}; 11 | 12 | sourcesFiles.forEach((file) => { 13 | const dir = path.dirname(file); 14 | const fileName = path.basename(file); 15 | const destFileName = fileName.replace(filenamePattern, config.general.bundleKey); 16 | const destFile = path.join(dir, destFileName); 17 | sources[destFile] = path.join(cwd, file); 18 | }); 19 | 20 | return sources; 21 | } 22 | 23 | return sourcesFiles; 24 | }; 25 | -------------------------------------------------------------------------------- /utils/checkChunk.test.js: -------------------------------------------------------------------------------- 1 | process.argv.push('--quiet'); 2 | const checkChunk = require("./checkChunk"); 3 | 4 | const vendor1 = './node_modules/mkFullPathSync/babel'; 5 | const vendor2 = './mkFullPathSync/node_modules/core-js'; 6 | const regular = './regular/test' 7 | const excluded = ['babel', 'core-js'] 8 | 9 | 10 | describe('Test utils/checkChunk.js', () => { 11 | 12 | it(`Should return false if a module comes empty`, () => { 13 | expect(checkChunk('', [])).toBe(false); 14 | }); 15 | 16 | it(`Should detect if a module is at included`, () => { 17 | expect(checkChunk(vendor1, [], ['node_modules'])).toBe(true); 18 | expect(checkChunk(vendor2, [], ['node_modules'])).toBe(true); 19 | }); 20 | 21 | it(`Should detect if a module comes from included but is excluded`, () => { 22 | expect(checkChunk(vendor1, [], ['node_modules'])).toBe(true); 23 | expect(checkChunk(vendor1, excluded, ['node_modules'])).toBe(false); 24 | }); 25 | 26 | it(`Should return false if is not on included `, () => { 27 | expect(checkChunk(regular, [excluded], ['node_modules'])).toBe(false); 28 | }); 29 | }); -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | const babel = require('./babel.config'); 2 | const clientlibs = require('./clientlibs.config'); 3 | const eslint = require('./eslint.config'); 4 | const general = require('./general.config.js'); 5 | const optimization = require('./optimization.config'); 6 | const output = require('./output.config'); 7 | const plugins = require('./plugins.config'); 8 | const postcss = require('./postcss.config'); 9 | const resolve = require('./resolve.config'); 10 | const sass = require('./sass.config'); 11 | const stylelint = require('./stylelint.config'); 12 | const templates = require('./templates.config'); 13 | const stats = require('./stats.config'); 14 | const cache = require('./cache.config'); 15 | const devServer = require('./devserver.config'); 16 | const performance = require('./performance.config'); 17 | 18 | const modules = general.modules; 19 | 20 | module.exports = { 21 | general, 22 | output, 23 | optimization, 24 | plugins, 25 | eslint, 26 | babel, 27 | modules, 28 | sass, 29 | clientlibs, 30 | stylelint, 31 | resolve, 32 | postcss, 33 | templates, 34 | stats, 35 | cache, 36 | devServer, 37 | performance 38 | }; 39 | -------------------------------------------------------------------------------- /utils/runStylelint.js: -------------------------------------------------------------------------------- 1 | const stylelint = require('stylelint'); 2 | const { log } = require('./log'); 3 | const linterError = require('./linterError'); 4 | 5 | module.exports = function runStylelint(files, projectConfig, cb) { 6 | // extract from config 7 | const { failOnError } = projectConfig.stylelint; 8 | const { rootPath } = projectConfig.general; 9 | 10 | if (projectConfig.general.disableStyleLint) { 11 | return cb(); 12 | } 13 | 14 | stylelint.lint({ 15 | files, 16 | configBasedir: rootPath, 17 | quietDeprecationWarnings: true 18 | }).then((data) => { 19 | if (!data.errored) return cb(); 20 | 21 | const fileError = JSON.parse(data.output); 22 | 23 | // show errors 24 | fileError 25 | .filter(errors => errors.warnings.length > 0) 26 | .map(error => error.warnings.forEach(er => linterError(er, error))); 27 | 28 | if (failOnError) { 29 | process.exit(1); 30 | } 31 | 32 | return fileError; 33 | }).catch(({ code, message }) => { 34 | log(__filename, 'error', message, 'error'); 35 | // If config file not provided, continue 36 | if (code === 78) { 37 | cb(); 38 | } 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /utils/getArgumentValue.test.js: -------------------------------------------------------------------------------- 1 | process.argv.push('--quiet'); 2 | 3 | const getArgumentValue = require("./getArgumentValue"); 4 | 5 | describe('Test utils/getArgumentValue.js', () => { 6 | 7 | it('Should return false, if argument object is empty or missing', () => { 8 | const lastArgs = [...process.argv]; 9 | process.argv = false; 10 | const arg = getArgumentValue('--example='); 11 | expect(arg).toBe(false); 12 | process.argv = lastArgs; 13 | }); 14 | it('Should return false if the argument is not passed', () => { 15 | const arg = getArgumentValue('--example'); 16 | expect(arg).toBe(false); 17 | }); 18 | 19 | it('Should return true if there a argument with no value', () => { 20 | const arg = getArgumentValue('--quiet'); 21 | expect(arg).toBe(true); 22 | }); 23 | 24 | it('Should return value, if there a argument with value', () => { 25 | const ArgValue = 'My nice passed arg string'; 26 | const argName = '--example='; 27 | process.argv.push(`${argName}${ArgValue}`); 28 | const arg = getArgumentValue('--example='); 29 | expect(arg).toBe(ArgValue); 30 | }); 31 | }) 32 | -------------------------------------------------------------------------------- /test/.febuild: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function (defaultConfig) { 4 | const rootPath = __dirname; 5 | const sourcesPath = path.join(rootPath, 'src'); 6 | const common = path.join(sourcesPath, 'common'); 7 | const includePaths = [path.join(common, 'styles'), defaultConfig.general.nodeModules]; 8 | 9 | return { 10 | general: { 11 | projectKey: 'febuild', 12 | rootPath, 13 | sourcesPath, 14 | destinationPath: path.join(rootPath, 'dist'), 15 | common, 16 | sourceKey: 'src', 17 | bundleKey: 'dist' 18 | }, 19 | sass: { 20 | includePaths 21 | }, 22 | resolve: { 23 | alias: { 24 | common 25 | } 26 | }, 27 | clientlibs: { 28 | override: true, 29 | skipCategories: ['febuild.author'] 30 | }, 31 | templates: { 32 | clientlibTemplate: (category, prefix) => ` 33 | ` 39 | } 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ '**' ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | install: 11 | 12 | runs-on: ${{ matrix.os }} 13 | 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest] 17 | node-version: [18.x] 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | - name: Setup Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - name: Install dependencies 27 | run: npm ci 28 | - name: Build Library 29 | run: npm run build --if-present 30 | 31 | test: 32 | runs-on: ${{ matrix.os }} 33 | 34 | strategy: 35 | matrix: 36 | os: [ubuntu-latest] 37 | node-version: [18.x] 38 | 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v2 42 | - name: Setup Node.js ${{ matrix.node-version }} 43 | uses: actions/setup-node@v3 44 | with: 45 | node-version: ${{ matrix.node-version }} 46 | - name: Install dependencies 47 | run: npm ci 48 | - name: test Library 49 | run: npm run test --if-present 50 | 51 | -------------------------------------------------------------------------------- /test/.febuild.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function (defaultConfig) { 4 | const rootPath = __dirname; 5 | const sourcesPath = path.join(rootPath, 'src'); 6 | const common = path.join(sourcesPath, 'common'); 7 | const includePaths = [path.join(common, 'styles'), defaultConfig.general.nodeModules]; 8 | 9 | return { 10 | general: { 11 | projectKey: 'febuild', 12 | rootPath, 13 | sourcesPath, 14 | destinationPath: path.join(rootPath, 'dist'), 15 | common, 16 | sourceKey: 'src', 17 | bundleKey: 'dist' 18 | }, 19 | sass: { 20 | includePaths 21 | }, 22 | resolve: { 23 | alias: { 24 | common 25 | } 26 | }, 27 | clientlibs: { 28 | override: true, 29 | skipCategories: ['febuild.author'] 30 | }, 31 | templates: { 32 | clientlibTemplate: (category, prefix) => ` 33 | ` 39 | } 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /config/optimization.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { isProduction, excludedFromVendors, bundleKey } = require('./general.config'); 3 | const checkChunk = require('../utils/checkChunk'); 4 | const nodeModules = `node_modules`; 5 | 6 | // curry to pass the module to check 7 | const test = ( excludes, includes) => (mod) => checkChunk(mod.context, excludes, includes); 8 | 9 | module.exports = { 10 | minimize: isProduction, 11 | usedExports: 'global', 12 | runtimeChunk: { 13 | name: `commons/treeshaking.${bundleKey}.js` 14 | }, 15 | splitChunks: { 16 | chunks: 'initial', 17 | minChunks: 2, 18 | cacheGroups: { 19 | // Treeshake vendors in node_modules (but keep unique vendors at the clientlibs it belongs) 20 | vendors: { 21 | test: test([nodeModules], excludedFromVendors), 22 | minChunks: 2, 23 | enforce : true, 24 | name: `commons/vendors.${bundleKey}.js`, 25 | // used on at least 2 modules 26 | }, 27 | // Treeshakes common imports, if used in more than 2 clientlibs 28 | treeshaking: { 29 | test: test([nodeModules]), 30 | minChunks: 2, 31 | enforce : true, 32 | name: `commons/treeshaking.${bundleKey}.js`, 33 | } 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: Test, Build and Release 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ ubuntu-latest ] 15 | node-version: [ 18.x ] 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix['node-version'] }} 24 | - name: Install dependencies 25 | run: npm ci 26 | - name: Build Library 27 | run: npm run build --if-present 28 | - name: Run Tests 29 | run: npm test --if-present 30 | - name: Release 31 | uses: cycjimmy/semantic-release-action@v3 32 | with: 33 | dry_run: false 34 | extra_plugins: | 35 | @semantic-release/changelog 36 | @semantic-release/git 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | GIT_AUTHOR_NAME: github-actions 41 | GIT_AUTHOR_EMAIL: github-actions@github.com 42 | GIT_COMMITTER_NAME: github-actions 43 | GIT_COMMITTER_EMAIL: github-actions@github.com 44 | CI: true 45 | -------------------------------------------------------------------------------- /utils/merge.test.js: -------------------------------------------------------------------------------- 1 | const merge = require("./merge"); 2 | 3 | let inicial = {}; 4 | let final = {}; 5 | let merged = {}; 6 | 7 | beforeEach(() => { 8 | inicial = { 9 | prop1: { 10 | prop1: { 11 | prop1: 'value', 12 | prop2: [1,2,4] 13 | } 14 | }, 15 | prop2: { 16 | prop1: { 17 | prop1: 'value', 18 | } 19 | } 20 | }; 21 | final = { 22 | prop1: { 23 | prop1: { 24 | prop1: true, 25 | } 26 | }, 27 | }; 28 | 29 | merged = merge(inicial,final); 30 | }) 31 | 32 | describe('Test utils/merge.js', () => { 33 | 34 | it(`With 2 level deep, prop is overriden by merge`, () => { 35 | expect(merged.prop1.prop1.prop1).toBe(final.prop1.prop1.prop1); 36 | }); 37 | 38 | it(`With if 2 level deep, inicial prop is ketp by merge`, () => { 39 | expect(merged.prop1.prop1.prop2).toBe(inicial.prop1.prop1.prop2); 40 | }); 41 | 42 | it(`With if 2 level deep, new prop is filled by merge`, () => { 43 | expect(merged.prop1.prop1.prop3).toBe(final.prop1.prop1.prop3); 44 | }); 45 | 46 | it(`Check if other levels are intact`, () => { 47 | expect(merged.prop2.prop1.prop1).toBe(inicial.prop2.prop1.prop1); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '20 12 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript' ] 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v3 35 | 36 | # Initializes the CodeQL tools for scanning. 37 | - name: Initialize CodeQL 38 | uses: github/codeql-action/init@v2 39 | with: 40 | languages: ${{ matrix.language }} 41 | 42 | - run: npm run build --if-present 43 | 44 | - name: Perform CodeQL Analysis 45 | uses: github/codeql-action/analyze@v2 46 | -------------------------------------------------------------------------------- /tasks/styles.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const styles = require('./styles'); 4 | const defaults = require('../config'); 5 | const extendConfig = require('../utils/extendConfig'); 6 | const generateEntries = require('../utils/generateEntries'); 7 | 8 | let config = extendConfig('./test/.febuild', defaults); 9 | let entries = { 10 | ...generateEntries(config, 'scss') 11 | }; 12 | const { destinationPath, projectKey } = config.general; 13 | 14 | beforeAll(async () => 15 | await new Promise(async (r) => { 16 | await styles(config); 17 | r(); 18 | }) 19 | ); 20 | 21 | describe('Test task/styles.js', () => { 22 | Object.keys(entries).forEach((entry) => { 23 | const file = path.join(destinationPath, entry); 24 | const source = entries[entry]; 25 | const ext = path.extname(file) === '.js' ? 'js' : 'css'; 26 | const fileName = `${file.split('.').slice(0, -1).join('.')}.${ext}`; 27 | it(`Compile ${source} file and save ${entry} at destination folder`, async () => { 28 | const bundleContent = fs.readFileSync(fileName, { encoding:'utf8', flag:'r' }); 29 | const sourceContent = fs.readFileSync(source, { encoding:'utf8', flag:'r' }); 30 | expect(bundleContent).not.toBe(sourceContent); 31 | }); 32 | }) 33 | }); 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tasks/clientlibs.js: -------------------------------------------------------------------------------- 1 | const { log } = require('../utils/log'); 2 | const generateEntries = require('../utils/generateEntries'); 3 | const getClientlib = require('../utils/getClientlib'); 4 | const renderClientLibs = require('../utils/renderClientLibs'); 5 | 6 | // extend log to proper say what file is running 7 | module.exports = (config) => { 8 | log(__filename, 'clientlibs task running...', '', 'info'); 9 | 10 | const { extraEntries } = config.postcss; 11 | 12 | // checking all entries at this configuration 13 | let entries = { 14 | ...generateEntries(config), 15 | ...generateEntries(config, 'scss'), 16 | }; 17 | 18 | entries = { 19 | ...entries, 20 | ...extraEntries ? { 21 | ...generateEntries(config, extraEntries.extension, extraEntries.filenamePattern, extraEntries.cwd), 22 | } : null, 23 | } 24 | 25 | // clientlibs to render 26 | const clientLibs = {}; 27 | 28 | // get parse to check if it has css or js or both. 29 | Object.keys(entries).forEach((entryKey) => { 30 | const { name, folder, fileName, extension } = getClientlib(entryKey); 31 | 32 | if (!clientLibs[folder]) { 33 | clientLibs[folder] = { name, folder }; 34 | } 35 | 36 | // set the extension 37 | clientLibs[folder][extension] = fileName; 38 | }); 39 | 40 | Object.keys(clientLibs).forEach(lib => renderClientLibs(clientLibs[lib], config)); 41 | }; 42 | -------------------------------------------------------------------------------- /utils/taskVerification.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { log, color } = require('./log'); 3 | 4 | module.exports = (configuration, taskFolder = '../tasks/') => { 5 | log(__filename, `Tasks started with mode - ${color('green', configuration.general.mode)}`); 6 | 7 | if (configuration && configuration.general && configuration.general.task) { 8 | try { 9 | /* eslint-disable */ 10 | // Exception to dinamic require of tasks 11 | const execute = require(`${taskFolder}${configuration.general.task}`); 12 | 13 | /* eslint-enable */ 14 | execute(configuration); 15 | } catch (e) { 16 | log(__filename, e.message, '', 'info'); 17 | 18 | try { 19 | /* eslint-disable */ 20 | // try as a abs path script (external tasks) project scripts and share config 21 | const execute = require(path.resolve(`${configuration.general.task}`)); 22 | 23 | /* eslint-enable */ 24 | execute(configuration); 25 | } catch (error) { 26 | log(__filename, `Cannot find task to execute ${configuration.general.task}`, '', 'error'); 27 | } 28 | } 29 | } else { 30 | // run all default default async 31 | configuration.general.defaultTasks.forEach((taskName) => { 32 | /* eslint-disable */ 33 | const task = require(`${taskFolder}${taskName}`); 34 | /* eslint-enable */ 35 | setTimeout(() => task(configuration)); 36 | }); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ## Description 7 | 8 | 9 | 10 | ## Related Issue 11 | 12 | 15 | 19 | 20 | Fixes # 21 | 22 | ## Types of changes 23 | 24 | 25 | 26 | - [ ] Bug fix (non-breaking change which fixes an issue) 27 | - [ ] New feature (non-breaking change which adds functionality) 28 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 29 | 30 | ## Checklist: 31 | 32 | 33 | 34 | - [ ] My code follows the code style of this project. 35 | - [ ] I have read the **[CONTRIBUTING](docs/CONTRIBUTING.md)** document. 36 | - [ ] All new and existing tests passed. 37 | -------------------------------------------------------------------------------- /tasks/webpack.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const webpackTask = require('./webpack'); 4 | const defaults = require('../config'); 5 | const extendConfig = require('../utils/extendConfig'); 6 | const generateEntries = require('../utils/generateEntries'); 7 | 8 | let config = extendConfig('./test/.febuild', defaults); 9 | let entries = { 10 | ...generateEntries(config, 'js') 11 | }; 12 | const { destinationPath, projectKey } = config.general; 13 | 14 | beforeAll(async () => { 15 | return await new Promise(async (resolve, reject) => { 16 | try { 17 | await webpackTask(config); 18 | resolve(); 19 | } catch (e) { 20 | reject(e); 21 | } 22 | }); 23 | }, 20000); 24 | 25 | describe('Test task/webpack.js', () => { 26 | Object.keys(entries).forEach((entry) => { 27 | const file = path.join(destinationPath, entry); 28 | const source = entries[entry]; 29 | const ext = path.extname(file) === '.js' ? 'js' : 'css'; 30 | const fileName = `${file.split('.').slice(0, -1).join('.')}.${ext}`; 31 | it(`Compile ${source} file and save ${entry} at destination folder`, async () => { 32 | const bundleContent = fs.readFileSync(fileName, { encoding:'utf8', flag:'r' }); 33 | const sourceContent = fs.readFileSync(source, { encoding:'utf8', flag:'r' }); 34 | expect(bundleContent).not.toBe(sourceContent); 35 | }); 36 | }) 37 | }); 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /utils/extendConfig.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { log } = require('./log'); 3 | const merge = require('./merge'); 4 | 5 | module.exports = (configPath, config) => { 6 | const dir = path.join(config.general.rootPath, configPath); 7 | 8 | /* eslint-disable */ 9 | // usually this is not a good options to have dynamic require, here is a exception 10 | const buildConfig = require(dir); 11 | const override = typeof buildConfig === 'function' ? buildConfig(config) : buildConfig; 12 | 13 | /* eslint-enable */ 14 | // extending the configs 15 | log(__filename, `Extending Fe build config for ${path.dirname(configPath)}`); 16 | 17 | // this allows our .febuild create its own config per folder 18 | // if sourcesPath is not provided, .febuild file location is used 19 | const { general = {} } = override; 20 | let basePath = general.sourcesPath; 21 | 22 | if (!general.sourcesPath) { 23 | override.general = override.general || {}; 24 | override.general.sourcesPath = path.dirname(dir); 25 | basePath = override.general.sourcesPath.split(config.general.rootPath)[1]; 26 | } 27 | 28 | if (!general.destinationPath) { 29 | const parts = basePath.split(path.sep); 30 | parts[1] = 'dist'; 31 | const dest = path.join(config.general.rootPath, ...parts); 32 | override.general = override.general || {}; 33 | override.general.destinationPath = dest; 34 | } 35 | 36 | // config merge 37 | const copyConfig = merge({}, config); 38 | const extendedConfig = merge(copyConfig, override); 39 | return extendedConfig; 40 | }; 41 | -------------------------------------------------------------------------------- /utils/renderPostcss.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | const { log } = require('./log'); 3 | const getPlugins = require('./getPlugins'); 4 | 5 | module.exports = function renderPostcss(input, outFile, config, cb) { 6 | const { plugins, failOnError } = config.postcss; 7 | 8 | try { 9 | // try to dynamic load of postcss plugins 10 | const runPlugins = getPlugins(plugins); 11 | const map = config.general.isProduction ? false : { inline: true, prev: input.sourceMap }; 12 | 13 | // run postcss plugins at sass output 14 | postcss(runPlugins).process(input.css.toString(), { from: outFile, map }) 15 | .then((result) => { 16 | // check all warnings 17 | const warns = result.warnings(); 18 | 19 | if (warns && warns.length > 0) { 20 | // log warnings 21 | return result.warnings().forEach((warn) => { 22 | log(__filename, 'error', warn.toString(), 'error'); 23 | // exit if fail on error is defined 24 | if (failOnError) { 25 | process.exit(1); 26 | } 27 | }); 28 | } 29 | 30 | // success and return 31 | log(__filename, 32 | `PostCSS applied: ${plugins.map(ent => (Array.isArray(ent) ? ent[0] : ent)).join(', ')}`, 33 | input.destFile, 34 | 'success'); 35 | 36 | return cb(result); 37 | }); 38 | } catch (error) { 39 | log(__filename, 'Postcss error', error.message, 'error'); 40 | // exit if fail on error is defined 41 | if (failOnError) { 42 | process.exit(1); 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /tasks/run.js: -------------------------------------------------------------------------------- 1 | const glob = require('fast-glob'); 2 | const config = require('../config'); 3 | const extendConfig = require('../utils/extendConfig'); 4 | const taskVerification = require('../utils/taskVerification'); 5 | const { log } = require('../utils/log'); 6 | 7 | // start log messages 8 | log(__filename, ' Looking for configuration files', ', if it finds its runs ', 'info'); 9 | 10 | // check if there is a params sent to run a single config file 11 | if (config && config.general && config.general.configFile) { 12 | log(__filename, ' Running configuration from parameter'); 13 | 14 | // load the configuration 15 | const configuration = extendConfig(config.general.configFile, config); 16 | 17 | // run the proper task on the sent config 18 | taskVerification(configuration); 19 | } else { 20 | // No configFile set then lookup for configurations files 21 | const configPattern = `**/${config.general.extendConfigurations}`; 22 | const availableBuilds = glob.sync([configPattern, `${configPattern}.js`], { cwd: config.general.rootPath, ignore: ['./node_modules/**'] }); 23 | 24 | // log what is happening 25 | if (availableBuilds.length === 0) { 26 | log(__filename, ' No configuration files found, running default configuration'); 27 | 28 | taskVerification(config); 29 | } else { 30 | log(__filename, ' Found local configurations', ' - Running them instead', 'warning'); 31 | 32 | // extending every found build options 33 | availableBuilds.forEach((configPath) => { 34 | const configuration = extendConfig(configPath, config); 35 | 36 | // check if a specific task was sent 37 | taskVerification(configuration); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /utils/renderClientLibs.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const { log, color } = require('./log'); 4 | const writeFile = require('./writeFile'); 5 | const mkFullPathSync = require('./mkFullPathSync'); 6 | 7 | module.exports = function renderClientLibs(clientLibObject, config) { 8 | // extract from config 9 | const { projectKey, destinationPath } = config.general; 10 | const { name, folder, js, scss } = clientLibObject; 11 | const { override } = config.clientlibs; 12 | const { clientlibTemplate } = config.templates; 13 | const absolutePath = path.join(destinationPath, folder); 14 | 15 | log(__filename, `checking ${color('cyan', `${projectKey}.${name}`)}`); 16 | 17 | // check if the folder already exist 18 | if (!fs.existsSync(absolutePath)) { 19 | log(__filename, `Folder for ${name} does not exist creating ${folder}`); 20 | 21 | mkFullPathSync(absolutePath); 22 | 23 | log(__filename, `Creating ${color('cyan', `${projectKey}.${name}`)}`); 24 | } 25 | 26 | // if there is a scss or js we write the css.txt 27 | // write css.txt 28 | if (scss) { 29 | writeFile(path.join(absolutePath, 'css.txt'), `${scss.replace('.scss', '.css')}`, override); 30 | } 31 | 32 | // write css.txt 33 | if (js) { 34 | writeFile(path.join(absolutePath, 'js.txt'), `${js}`, override); 35 | } 36 | 37 | const extensionFile = config.postcss.extraEntries?.extension; 38 | const hasExtraEntries = extensionFile && clientLibObject[extensionFile]; 39 | const fileName = clientLibObject[extensionFile]; 40 | if ( hasExtraEntries ) { 41 | writeFile(path.join(absolutePath, `${extensionFile}.txt`), `${fileName}`, override); 42 | } 43 | 44 | // write .content.xml 45 | const content = clientlibTemplate(name, projectKey); 46 | writeFile(path.join(absolutePath, '.content.xml'), content, override); 47 | }; -------------------------------------------------------------------------------- /utils/getPlugins.test.js: -------------------------------------------------------------------------------- 1 | process.argv.push('--quiet'); 2 | 3 | describe('Test getPlugins.js', () => { 4 | beforeEach(() => { 5 | jest.resetModules(); 6 | }); 7 | 8 | it('Should load a plugin from string', () => { 9 | jest.mock('postcss-fe-build-test-a', () => () => 'mocked-plugin-a', { virtual: true }); 10 | 11 | const getPlugins = require('./getPlugins'); 12 | const plugins = ['postcss-fe-build-test-a']; 13 | const result = getPlugins(plugins); 14 | 15 | expect(result).toHaveLength(1); 16 | expect(result[0]()).toBe('mocked-plugin-a'); 17 | }); 18 | 19 | it('Should load a plugin from [plugin, options] format', () => { 20 | jest.mock('postcss-fe-build-test-b', () => ({ 21 | default: (opts) => `mocked-plugin-b:${JSON.stringify(opts)}` 22 | }), { virtual: true }); 23 | 24 | const getPlugins = require('./getPlugins'); 25 | const plugins = [['postcss-fe-build-test-b', { extractAll: false }]]; 26 | const result = getPlugins(plugins); 27 | 28 | expect(result).toHaveLength(1); 29 | expect(result[0]).toBe('mocked-plugin-b:{"extractAll":false}'); 30 | }); 31 | 32 | 33 | it('Should ignore falsy values in plugin list', () => { 34 | jest.mock('postcss-fe-build-test-c', () => () => 'mocked-plugin-a', { virtual: true }); 35 | 36 | const getPlugins = require('./getPlugins'); 37 | const plugins = [null, undefined, false, 'postcss-fe-build-test-c']; 38 | const result = getPlugins(plugins); 39 | 40 | expect(result).toHaveLength(1); 41 | expect(result[0]()).toBe('mocked-plugin-a'); 42 | }); 43 | 44 | it('Should throw on invalid plugin entry', () => { 45 | const getPlugins = require('./getPlugins'); 46 | 47 | const invalidPlugins = [{ foo: 'bar' }, 123, true]; 48 | invalidPlugins.forEach(plugin => { 49 | expect(() => getPlugins([plugin])).toThrow('Invalid plugin entry'); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /utils/generateEntries.test.js: -------------------------------------------------------------------------------- 1 | process.argv.push('--quiet'); 2 | 3 | const defaults = require('../config'); 4 | const extendConfig = require('./extendConfig'); 5 | const generateEntries = require('./generateEntries'); 6 | 7 | const config = extendConfig('./test/.febuild', defaults); 8 | 9 | describe('Test utils/generateEntries.js', () => { 10 | it('Should throw an error if there is no config', () => { 11 | expect(generateEntries).toThrowError(); 12 | }); 13 | 14 | it('Should find no entries if there is no files with txt extension', () => { 15 | const entries = generateEntries(config, 'txt'); 16 | expect(Object.keys(entries).length).toBe(0); 17 | }); 18 | 19 | it('Should find 2 javascripts entries at ./test', () => { 20 | const entries = generateEntries(config); 21 | expect(Object.keys(entries).length).toBe(2); 22 | }); 23 | 24 | it('Should find 3 SCSS entries at ./test', () => { 25 | const entries = generateEntries(config,'scss'); 26 | expect(Object.keys(entries).length).toBe(3); 27 | 28 | }); 29 | 30 | it(`Destination file should be based on same name pattern as source file`, () => { 31 | const entries = generateEntries(config); 32 | const passed = Object.keys(entries).reduce((pass, key) => { 33 | const value = entries[key]; 34 | const destination = key.replace(`.${config.general.bundleKey}`, ''); 35 | const source = value.replace(`.${config.general.sourceKey}`,''); 36 | return pass && source.indexOf(destination) >= 0; 37 | }, true) 38 | expect(passed).toBe(true); 39 | }); 40 | 41 | it('Generate file list for single bundle build at ./test, eg [file1,file2]', () => { 42 | config.general.multiple = false; 43 | const entries = generateEntries(config); 44 | expect(Array.isArray(entries)).toBe(true); 45 | }); 46 | }) -------------------------------------------------------------------------------- /docs/tasks.md: -------------------------------------------------------------------------------- 1 | # Available NPM Tasks 2 | 3 | The following npm tasks are available by default after installing fe-build, but you need to add them manually to your package.json file. 4 | 5 | ## Compile JavaScript/ECMAScript 6 | 7 | Add `nc-fe-build --task=webpack` task in package.json scripts 8 | 9 | ```json 10 | "scripts": { 11 | "build:js": "nc-fe-build --task=webpack" 12 | }, 13 | ``` 14 | 15 | To execute it, open the terminal and run `npm run build:js` from the same folder where the file package.json is. 16 | 17 | ## Compile Sass 18 | 19 | ```json 20 | "scripts": { 21 | "build:css": "nc-fe-build --task=styles" 22 | }, 23 | ``` 24 | 25 | ## Create ClientLibrary Files 26 | 27 | ```json 28 | "scripts": { 29 | "build:clientlibs": "nc-fe-build --task=clientlibs" 30 | } 31 | ``` 32 | 33 | ## Run an Specific Config File 34 | 35 | You can run the build from a single configuration file: 36 | 37 | ```json 38 | "scripts": { 39 | "build:config": "nc-fe-build --config-file=path/to/configFile" 40 | } 41 | ``` 42 | 43 | ## Watch Sass and JS 44 | 45 | ```json 46 | "scripts": { 47 | "watch:js": "nc-fe-build --task=webpack --watch", 48 | "watch:css": "nc-fe-build --task=styles --watch", 49 | } 50 | ``` 51 | 52 | ## Analyze JS 53 | 54 | Analyze the bundles with [Webpack Bundle Analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer). 55 | 56 | ```json 57 | "scripts": { 58 | "analyze": "nc-fe-build --task=webpack --analyze --development" 59 | } 60 | ``` 61 | 62 | To change the port number, you can append the argument `--port=` followed by a valid port number. 63 | 64 | # Additional Arguments 65 | You can add the following arguments to the `nc-fe-build` command in order to change its behavior. 66 | 67 | | Argument | Description | 68 | | --------- | ----------- | 69 | | `--quiet` | Displays less messages in the output | 70 | | `--disable-styelint` | Disables Stylelint | 71 | -------------------------------------------------------------------------------- /tasks/clientlibs.test.js: -------------------------------------------------------------------------------- 1 | process.argv.push('--quiet'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const clientlibTask = require('./clientlibs'); 5 | const defaults = require('../config'); 6 | const extendConfig = require('../utils/extendConfig'); 7 | const generateEntries = require('../utils/generateEntries'); 8 | const getClientlib = require('../utils/getClientlib'); 9 | 10 | let config = extendConfig('./test/.febuild', defaults); 11 | let entries = { 12 | ...generateEntries(config), 13 | ...generateEntries(config, 'scss') 14 | }; 15 | const { destinationPath, projectKey } = config.general; 16 | const { clientlibTemplate } = config.templates; 17 | // clear 18 | beforeAll(async () => { 19 | await clientlibTask(config); 20 | }); 21 | 22 | describe('Test task/clientlibs.js', () => { 23 | Object.keys(entries).forEach((entry) => { 24 | const file = path.join(destinationPath, entry); 25 | const type = path.extname(file); 26 | const dir = path.dirname(file); 27 | const { name, fileName } = getClientlib(entry); 28 | const ext = type == '.js' ? 'js' : 'css'; 29 | const txtContet = `${fileName.split('.').slice(0, -1).join('.')}.${ext}`; 30 | const txtPath = path.join(dir, `${ext}.txt`); 31 | 32 | it(`TXT files should be created and point to ${entry} file`, () => { 33 | const fileContent = fs.readFileSync(txtPath, { encoding:'utf8', flag:'r' }); 34 | expect(fileContent).toBe(txtContet); 35 | }); 36 | 37 | it(`Should create .content.xml files with it's category "${name}" based on template`, () => { 38 | const template = clientlibTemplate(name, projectKey); 39 | const fileContent = fs.readFileSync(path.join(dir,'.content.xml'), { encoding:'utf8', flag:'r' }); 40 | expect(fileContent).toBe(template); 41 | }); 42 | }) 43 | }); 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /utils/extendConfig.test.js: -------------------------------------------------------------------------------- 1 | process.argv.push('--quiet'); 2 | 3 | const json = require('./../test/.febuild'); 4 | const minimal = require('./../test/src/minimal/.febuild'); 5 | const config = require('../config'); 6 | const extendConfig = require('./extendConfig'); 7 | const finalValues = json(config); 8 | 9 | 10 | describe('Test utils/extendConfig.js', () => { 11 | it('Should throw an error no .febuild file is found ', () => { 12 | expect(() => extendConfig('.febuild', config)).toThrowError(); 13 | }); 14 | 15 | it('Should find a .febuild file, read and merge into defaults values', () => { 16 | const newConfig = extendConfig('./test/.febuild', config); 17 | const reducer = (o,f) => { 18 | return Object.keys(o).reduce((v,k) => { 19 | if (typeof o[k] === 'function') return v && true; 20 | if (typeof o[k] !== 'object' || Array.isArray(o[k])) return v && JSON.stringify(f[k]) === JSON.stringify(o[k]); 21 | return v && reducer(o[k],f[k]); 22 | }, true); 23 | } 24 | const test = reducer(finalValues, newConfig); 25 | expect(test).toBe(true); 26 | }); 27 | 28 | it('should work even if no destination path or source paths are configured', () => { 29 | delete config.general.destinationPath; 30 | delete config.general.sourcesPath; 31 | const newConfig = extendConfig('./test/src/minimal/.febuild', config); 32 | const reducer = (o,f) => { 33 | return Object.keys(o).reduce((v,k) => { 34 | if (typeof o[k] === 'function') return v && true; 35 | if (typeof o[k] !== 'object' || Array.isArray(o[k])) { 36 | return v && JSON.stringify(f[k]) === JSON.stringify(o[k]); 37 | } 38 | return v && reducer(o[k],f[k]); 39 | }, true); 40 | } 41 | const test = reducer(minimal, newConfig); 42 | expect(test).toBe(true); 43 | }); 44 | }); -------------------------------------------------------------------------------- /tasks/styles.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { log } = require('../utils/log'); 3 | const generateEntries = require('../utils/generateEntries'); 4 | const renderStyles = require('../utils/renderStyles'); 5 | 6 | // extend log to proper say what file is running 7 | module.exports = (config) => { 8 | return new Promise((resolve) => { 9 | if (config && config.general && config.general.watch) { 10 | try { 11 | log(__filename, 'Watcher Sass / autoprefixer running...', '', 'info'); 12 | 13 | const gaze = require('gaze'); 14 | const sassPattern = path.join(config.general.sourcesPath, `**/*.${config.general.sourceKey}.scss`); 15 | 16 | gaze(sassPattern, function () { 17 | // simple debounce with timeout for only save the last if several events are triggered 18 | this.on('all', (event, file) => { 19 | // trigger a save 20 | const relativePath = path.relative(config.general.sourcesPath, path.dirname(file)); 21 | const fileName = path.basename(file) 22 | .replace(config.general.sourceKey, config.general.bundleKey); 23 | const destFile = path.join(relativePath, fileName); 24 | 25 | // override to keep alive 26 | config.stylelint.failOnError = false; 27 | 28 | renderStyles(file, destFile, config); 29 | }); 30 | }); 31 | } catch (e) { 32 | log(__filename, 'Something is missing, you need install dev dependencies for this.', e.message, 'error'); 33 | } 34 | } else { 35 | log(__filename, 'Sass / autoprefixer running...', '', 'info'); 36 | 37 | // checking all entries at this configuration 38 | const entries = generateEntries(config, 'scss'); 39 | const promises = Object.keys(entries).map(file => renderStyles(entries[file], file, config)); 40 | Promise.allSettled(promises).then((results) => { 41 | log(__filename, 'Styles done', '', 'info'); 42 | resolve(); 43 | }); 44 | } 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@netcentric/fe-build", 3 | "version": "5.3.1", 4 | "description": "Frontend build tools for AEM projects.", 5 | "license": "Apache-2.0", 6 | "author": "Cognizant Netcentric (https://www.netcentric.biz)", 7 | "private": false, 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/Netcentric/fe-build.git " 14 | }, 15 | "scripts": { 16 | "clean": "rm -rf ./test/dist", 17 | "test": "npm run clean && npm run jest", 18 | "jest": "NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest -u --runInBand" 19 | }, 20 | "keywords": [ 21 | "build" 22 | ], 23 | "files": [ 24 | "/config", 25 | "/utils", 26 | "/tasks", 27 | "/cli", 28 | "/docs", 29 | "/hooks", 30 | "./README.md", 31 | "LICENSE" 32 | ], 33 | "bin": { 34 | "nc-fe-build": "./cli/index.js" 35 | }, 36 | "paths": { 37 | "configs": "./config", 38 | "tasks": "./tasks" 39 | }, 40 | "dependencies": { 41 | "@babel/core": "^7.26.7", 42 | "@babel/plugin-transform-runtime": "^7.25.9", 43 | "@babel/preset-env": "^7.26.7", 44 | "autoprefixer": "^10.4.20", 45 | "babel-loader": "9.1.2", 46 | "core-js": "^3.40.0", 47 | "eslint": "^8.57.1", 48 | "eslint-webpack-plugin": "^4.2.0", 49 | "fast-glob": "^3.3.3", 50 | "gaze": "^1.1.3", 51 | "jest": "^29.7.0", 52 | "postcss": "^8.5.1", 53 | "sass": "^1.83.4", 54 | "semver": "^7.6.3", 55 | "stylelint": "^16.14.1", 56 | "webpack": "^5.97.1" 57 | }, 58 | "devDependencies": { 59 | "del-cli": "^6.0.0", 60 | "stylelint-config-standard-scss": "^14.0.0", 61 | "webpack-bundle-analyzer": "^4.10.2", 62 | "webpack-cli": "^6.0.1" 63 | }, 64 | "overrides": { 65 | "semver": "^7.6.3" 66 | }, 67 | "browserslist": [ 68 | "last 2 versions", 69 | "not ie > 0", 70 | "not ie_mob > 0", 71 | "not dead", 72 | "not OperaMini all" 73 | ], 74 | "engines": { 75 | "node": ">=18.12.0" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /utils/log.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { rootPath, quiet } = require('../config/general.config'); 3 | 4 | const colorOptions = { 5 | bgblack: '\x1b[40m', 6 | bgblue: '\x1b[44m', 7 | bgcyan: '\x1b[46m', 8 | bggreen: '\x1b[42m', 9 | bgmagenta: '\x1b[45m', 10 | bgred: '\x1b[41m', 11 | bgwhite: '\x1b[47m', 12 | bgyellow: '\x1b[43m', 13 | black: '\x1b[30m', 14 | blink: '\x1b[5m', 15 | blue: '\x1b[34m', 16 | bright: '\x1b[1m', 17 | cyan: '\x1b[36m', 18 | dim: '\x1b[2m', 19 | green: '\x1b[32m', 20 | hidden: '\x1b[8m', 21 | magenta: '\x1b[35m', 22 | red: '\x1b[31m', 23 | reset: '\x1b[0m', 24 | reverse: '\x1b[7m', 25 | underscore: '\x1b[4m', 26 | white: '\x1b[37m', 27 | yellow: '\x1b[33m' 28 | }; 29 | 30 | const color = (c, str, bg = colorOptions.reset) => `${colorOptions[c]}${str}${bg}`; 31 | 32 | const emojis = { 33 | error: [' ! '], 34 | success: [' ✔ '], 35 | }; 36 | 37 | const random = array => array[Math.floor(Math.random() * array.length)]; 38 | 39 | const severity = { 40 | info: { 41 | icon: () => color('dim', '☞ '), 42 | color: 'dim' 43 | }, 44 | warning: { 45 | icon: () => color('yellow', '⚠ '), 46 | color: 'yellow' 47 | }, 48 | log: { 49 | icon: () => color('yellow', '⚠ '), 50 | color: 'white' 51 | }, 52 | success: { 53 | icon: () => color('dim', `${(emojis.success)} -> `), 54 | color: 'green' 55 | }, 56 | error: { 57 | icon: () => color('red', '✖ '), 58 | color: 'red' 59 | } 60 | }; 61 | 62 | const log = (file = false, title, extra = '', sev = 'info', force = false) => { 63 | const fileRef = file ? `[${path.relative(rootPath, file)}] ` : ''; 64 | const fun = sev === 'error' ? sev : 'log'; 65 | if (!quiet || force) { 66 | const icon = severity[sev] ? severity[sev].icon() : ''; 67 | console[fun](`${color('dim', fileRef)}${icon}${color(severity[sev].color, title)}${color('dim', extra)}`); 68 | } 69 | }; 70 | 71 | module.exports = { 72 | colorOptions, 73 | color, 74 | emojis, 75 | random, 76 | severity, 77 | log 78 | }; 79 | -------------------------------------------------------------------------------- /utils/renderSass.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const sass = require('sass'); 3 | const mkFullPathSync = require('./mkFullPathSync'); 4 | const writeFile = require('./writeFile'); 5 | const { log } = require('./log'); 6 | 7 | module.exports = function renderSass(dest, file, config, cb, write = false) { 8 | // extract sass only configs 9 | const { outputStyle, includePaths, failOnError, adicionalOptions } = config.sass; 10 | 11 | // proper extension 12 | const destFile = dest.replace('.scss', '.css'); 13 | 14 | // url of the saved file 15 | const outFile = path.join(config.general.destinationPath, destFile); 16 | 17 | 18 | // extract from config 19 | const compiled = sass.compileAsync(file, { 20 | outputStyle, 21 | loadPaths:includePaths, 22 | sourceMap: !config.general.isProduction, 23 | // adicional config from https://sass-lang.com/documentation/js-api/interfaces/options/ 24 | ...adicionalOptions 25 | }); 26 | 27 | compiled.then((result) => { 28 | // create folder if it does not exist 29 | mkFullPathSync(path.dirname(outFile)); 30 | 31 | // write the css file, overriding it if write is enabled 32 | // its better to skip this so we only write the css once reducing I/O 33 | if (write) { 34 | writeFile(outFile, result.css, true); 35 | } 36 | 37 | // if is dev add source maps 38 | if (!config.general.isProduction && write) { 39 | writeFile(`${outFile}.map`, JSON.stringify(result.sourceMap), true); 40 | } 41 | 42 | // pass the destination relative for map 43 | result.destFile = destFile; 44 | 45 | // log and call back 46 | log(__filename, `Sass rendered - ${path.basename(destFile)}`, ` -- `, 'success'); 47 | return cb(result, outFile); 48 | 49 | }).catch((error) => { 50 | log(__filename, `${destFile} ${error.message}!`, '', 'error'); 51 | // if set to exit on error, you might not want to exit on all cases 52 | if (failOnError) { 53 | process.exit(1); 54 | } else { 55 | log(__filename, `Sass file not rendered - ${path.basename(destFile)}`, ``, 'error'); 56 | // skip the rest 57 | return; 58 | } 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for choosing to contribute! 4 | 5 | The following are a set of guidelines to follow when contributing to this project. 6 | 7 | ## Code Of Conduct 8 | 9 | This project adheres to the [code of conduct](CODE_OF_CONDUCT.md). By participating, 10 | you are expected to uphold this code. 11 | 12 | ## Have A Question? 13 | 14 | Start by filing an issue. The existing committers on this project work to reach 15 | consensus around project direction and issue solutions within issue threads 16 | (when appropriate). 17 | 18 | ## getting started 19 | 1 - Update the git hooks for commit header and pre commit actions 20 | 21 | Start by running the follow commands to add hooks to git 22 | ```mkdir -p .git && mkdir -p .git/hooks && cp ./hooks/pre-commit ./.git/hooks/pre-commit``` 23 | 24 | ## Automated Release 25 | 26 | ### Release 27 | - based on Angular Commit Message Conventions in commits - 28 | https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-header 29 | - Commit message format is used to build: 30 | * Release notes 31 | * Changelog updates 32 | * NPM package semver 33 | 34 | ### Commit message Convention 35 | 36 | ``` 37 | (): 38 | │ │ │ 39 | │ │ └─⫸ Summary in present tense. Not capitalized. No period at the end. 40 | │ │ 41 | │ └─⫸ Commit Scope (optional): config|utils|tasks 42 | │ 43 | └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test 44 | ``` 45 | 46 | #### Major Version Release: 47 | 48 | In order to trigger Major Version upgrade, `BREAKING CHANGE:` needs to be in the footer of a commit message: 49 | 50 | ``` 51 | (): 52 | 53 | BREAKING CHANGE: 54 | ``` 55 | 56 | ## Code Reviews 57 | 58 | All submissions should come in the form of pull requests and need to be reviewed 59 | by project committers. Please always open a related issue before creating a pull Request. 60 | Read [GitHub's pull request documentation](https://help.github.com/articles/about-pull-requests/) 61 | for more information on sending pull requests. 62 | 63 | Lastly, please follow the [pull request template](.github/PULL_REQUEST_TEMPLATE.md) when 64 | submitting a pull request! 65 | -------------------------------------------------------------------------------- /tasks/webpack.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const ESLintPlugin = require('eslint-webpack-plugin'); 3 | 4 | const { log } = require('../utils/log'); 5 | const generateEntries = require('../utils/generateEntries'); 6 | // extend log to proper say what file is running 7 | module.exports = (config) => { 8 | return new Promise((r) => { 9 | // checking all entries at this configuration 10 | const entry = generateEntries(config); 11 | 12 | // make sure destination path is the same config as output 13 | if (config && config.general && config.general.destinationPath) { 14 | config.output.path = config.general.destinationPath; 15 | } 16 | 17 | // setting rules for modules 18 | const module = { 19 | rules: config.general.modules.map(rule => config[rule]) 20 | }; 21 | 22 | // log at the beginning 23 | log(__filename, 'Webpack transpile running...', '', 'info'); 24 | 25 | // extract from flatten configs to webpack 26 | const { output, plugins, optimization, resolve, externals, stats, performance, cache, devServer, eslint } = config; 27 | const { mode, watch, devtool } = config.general; 28 | // check if there is any entry 29 | plugins.push(new ESLintPlugin(eslint)); 30 | 31 | if (entry && Object.keys(entry).length > 0) { 32 | // run webpack 33 | webpack({ 34 | mode, 35 | watch, 36 | entry, 37 | output, 38 | module, 39 | plugins, 40 | optimization, 41 | devtool, 42 | resolve, 43 | performance, 44 | stats, 45 | cache, 46 | devServer, 47 | ...externals && { externals } 48 | }, (err, stats) => { 49 | // output the resulting stats. 50 | if (stats && stats.toString) { 51 | log(__filename, stats.toString({ colors: true })); 52 | } 53 | 54 | if (!watch && (stats && stats.hasErrors())) { 55 | log(__filename, stats.toString(), '', 'error'); 56 | process.exit(1); 57 | } 58 | 59 | if (err) { 60 | log(__filename, err.toString(), '', 'error'); 61 | process.exit(1); 62 | } 63 | 64 | // log completion 65 | log(__filename, 'Webpack transpile ended', '', 'success'); 66 | r(); 67 | }); 68 | } else { 69 | log(__filename, 'No entries for webpack, nothing found', '', 'info'); 70 | r(); 71 | } 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /.github/workflows/manual-release.yml: -------------------------------------------------------------------------------- 1 | name: Manual Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: 'Version' 7 | type: choice 8 | required: true 9 | default: fix 10 | options: 11 | - fix 12 | - feat 13 | - BREAKING CHANGE 14 | dryRun: 15 | description: 'DryRun' 16 | type: boolean 17 | default: true 18 | # ENV and Config 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | GIT_AUTHOR_NAME: github-actions 23 | GIT_AUTHOR_EMAIL: github-actions@github.com 24 | GIT_COMMITTER_NAME: github-actions 25 | GIT_COMMITTER_EMAIL: github-actions@github.com 26 | CI: true 27 | CONFIG_NODE_VERSION: '["18.x"]' 28 | CONFIG_OS: '["ubuntu-latest"]' 29 | # Main Job 30 | jobs: 31 | config: 32 | runs-on: ubuntu-latest 33 | outputs: 34 | NODE_VERSION: ${{ steps.set-config.outputs.CONFIG_NODE_VERSION }} 35 | OS: ${{ steps.set-config.outputs.CONFIG_OS }} 36 | steps: 37 | - id: set-config 38 | run: | 39 | echo "CONFIG_NODE_VERSION=${{ toJSON(env.CONFIG_NODE_VERSION) }}" >> $GITHUB_OUTPUT 40 | echo "CONFIG_OS=${{ toJSON(env.CONFIG_OS) }}" >> $GITHUB_OUTPUT 41 | release: 42 | name: Test, Build and force Release 43 | needs: config 44 | 45 | runs-on: ${{ matrix.OS }} 46 | strategy: 47 | matrix: 48 | OS: ${{ fromJSON(needs.config.outputs.OS) }} 49 | NODE_VERSION: ${{ fromJSON(needs.config.outputs.NODE_VERSION) }} 50 | 51 | steps: 52 | - name: Checkout repo 53 | uses: actions/checkout@v3 54 | - name: Setup Node.js ${{ matrix.NODE_VERSION }} 55 | uses: actions/setup-node@v3 56 | with: 57 | node-version: ${{ matrix.NODE_VERSION }} 58 | - name: Commit trigger 59 | run: | 60 | git commit --allow-empty -m "${{ github.event.inputs.version }}: Trigger Manual Release 61 | 62 | ${{ github.event.inputs.version }}:Forced Manual Release without code changes" 63 | - name: Install dependencies 64 | run: npm ci 65 | - name: Build Library 66 | run: npm run build --if-present 67 | - name: Run Tests 68 | run: npm test --if-present 69 | - name: Publish npm package 70 | uses: cycjimmy/semantic-release-action@v3 71 | with: 72 | dry_run: ${{ github.event.inputs.dryRun == 'true' }} 73 | extra_plugins: | 74 | @semantic-release/changelog 75 | @semantic-release/git 76 | -------------------------------------------------------------------------------- /docs/quick_start.md: -------------------------------------------------------------------------------- 1 | # Quick start 2 | 3 | The first configurations that you need to adapt are probably the source and destination paths. 4 | The default paths are `src` for the source path and `dist` for the destination path. If you have a different structure, you can override these values in your `.febuild` file. 5 | 6 | e.g. In your project: 7 | 8 | ``` 9 | -- package.json 10 | -- projectSrcDir 11 | |-- component 12 | |-- file.scss 13 | ``` 14 | 15 | Note that on the first execution of the `npm run build` task, probably no files will be processed, because no file will match with default settings. 16 | 17 | To start the build and change the default settings, add a `.febuild` file one level up from where your `projectSrcDir` directory is with the following content: 18 | 19 | ```javascript 20 | module.exports = {} 21 | ``` 22 | 23 | You can check the default settings for each specific section in the [configuration](./docs/configuration.md) documentation. 24 | 25 | ## Custom Source Path 26 | 27 | In order to start processing the files in your project, two updates are needed: 28 | 29 | 1. Add the `source` suffix to all the files that needs to be processed. This suffix value is defined in `general.sourceKey`. 30 | e.g.: `file.scss` --> `file.source.scss` 31 | 2. In the `.febuild` file, change the source directory `projectSrcDir` configuration to the path to where your source code is. 32 | 33 | ```javascript 34 | module.exports = { 35 | general: { 36 | sourcesPath: './projectSrcDir', 37 | } 38 | } 39 | ``` 40 | 41 | if `sourcePath` is not provided, the path where the `.febuild` file is will be used instead. For this example it will be enough. 42 | 43 | After running again the `npm run build` task, this will be the output the destination folder: 44 | 45 | ``` 46 | -- package.json 47 | -- .febuild 48 | -- projectSrcDir 49 | |-- component 50 | |-- file.source.scss 51 | -- dist 52 | |-- component 53 | |-- file.bundle.scss 54 | ``` 55 | 56 | ## Custom Destination Path 57 | 58 | To add a custom destination path, add to the `.febuild` file the property `general.destinationPath` with the desired path. 59 | 60 | ```javascript 61 | module.exports = { 62 | general: { 63 | sourcesPath: './projectSrcDir', 64 | destinationPath: path.resolve(__dirname, '..', 'custom', 'dist', 'path') 65 | } 66 | } 67 | ``` 68 | 69 | Then execute the `npm run build` task and check the results. 70 | 71 | ``` 72 | -- package.json 73 | -- .febuild 74 | -- projectSrcDir 75 | |-- component 76 | |-- file.source.scss 77 | -- custom 78 | |-- dist 79 | |-- path 80 | |-- component 81 | |-- file.bundle.scss 82 | ``` 83 | 84 | For more customizations, please check the [Configuration document](./configuration.md). 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @netcentric/fe-build 2 | 3 | Frontend build tools for AEM projects. 4 | 5 | [![Version](https://img.shields.io/npm/v/@netcentric/fe-build.svg)](https://npmjs.org/package/@netcentric/fe-build) 6 | [![Build Status](https://github.com/netcentric/fe-build/workflows/CI/badge.svg?branch=main)](https://github.com/netcentric/fe-build/actions) 7 | [![CodeQL Analysis](https://github.com/netcentric/fe-build/workflows/CodeQL/badge.svg?branch=main)](https://github.com/netcentric/fe-build/actions) 8 | [![semver: semantic-release](https://img.shields.io/badge/semver-semantic--release-blue.svg)](https://github.com/semantic-release/semantic-release) 9 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 10 | 11 | ## Abstract 12 | All-in-one solution for modern Frontend projects, with special focus on [Adobe Experience Manager](https://business.adobe.com/products/experience-manager/adobe-experience-manager.html) development (AEM). It compiles your Scss and JS source files and creates the appropriate [clientLibs](https://experienceleague.adobe.com/docs/experience-manager-65/developing/introduction/clientlibs.html?lang=en) to be included by AEM. 13 | 14 | ## Getting started 15 | Use [npm](https://docs.npmjs.com/about-npm/) to install fe-build: 16 | ``` 17 | npm i @netcentric/fe-build 18 | ``` 19 | 20 | Add the `nc-fe-build` task in the scripts section of your package.json file: 21 | ```json 22 | "scripts": { 23 | "build": "nc-fe-build" 24 | }, 25 | ``` 26 | 27 | Create a directory named `src` and put some Scss and JS files there named "*.source.scss" and "*.source.js". 28 | 29 | Create a file named `.febuild` file one level up from where the source code directory is with the following content: 30 | ```javascript 31 | module.exports = {} 32 | ``` 33 | 34 | Finally, run the build task: 35 | ```bash 36 | npm run build 37 | ``` 38 | 39 | ## Features 40 | ### JavaScript 41 | 42 | - Lint source code with [ESLint](https://eslint.org/). 43 | - Transpilation with [Babel](https://babeljs.io/). 44 | - [core-js](https://github.com/zloirock/core-js) polyfills are automatically added when needed. 45 | - Bundled and optimized with [Webpack](https://webpack.js.org/). 46 | - Analyze bundles with [Webpack Bundle Analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer). 47 | 48 | ### CSS 49 | 50 | - Lint source code with [Stylelint](https://stylelint.io/). 51 | - [SASS](https://sass-lang.com/) compilation. 52 | - Add vendor prefixes with [Autoprefixer](https://github.com/postcss/autoprefixer). 53 | 54 | ### ClientLibraries 55 | 56 | - Automatically create [clientLibrary resources](https://experienceleague.adobe.com/docs/experience-manager-65/developing/introduction/clientlibs.html?lang=en) based on the source files. 57 | - Include all generated CSS and JS files in the css.txt and js.txt files. 58 | 59 | ## Guides 60 | 61 | + [Configuration](./docs/configuration.md) 62 | + [Recommended NPM Tasks](./docs/tasks.md) 63 | + [Quick Start](./docs/quick_start.md) 64 | + [Migrating to v4.0.0](./docs/migration.md) 65 | + [Contributing](./docs/CONTRIBUTING.md) 66 | 67 | -------------------------------------------------------------------------------- /utils/renderPostcss.test.js: -------------------------------------------------------------------------------- 1 | 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const defaults = require('../config'); 5 | const extendConfig = require('../utils/extendConfig'); 6 | const renderPostcss = require('./renderPostcss'); 7 | // prevent logs from extend config 8 | console.log = jest.fn(); 9 | const config = extendConfig('./test/.febuild', defaults); 10 | const outFile = path.resolve(`./test/dist/postcss/component.dist.css`); 11 | const inputContent = {css: `::placeholder { 12 | color: gray; 13 | } 14 | 15 | .image { 16 | background-image: url(image@1x.png); 17 | } 18 | 19 | @media (min-resolution: 2dppx) { 20 | .image { 21 | background-image: url(image@2x.png); 22 | } 23 | }`}; 24 | 25 | const outputContent = '::-moz-placeholder {\n' + 26 | ' color: gray;\n' + 27 | '}\n' + 28 | '\n' + 29 | '::placeholder {\n' + 30 | ' color: gray;\n' + 31 | '}\n' + 32 | '\n' + 33 | '.image {\n' + 34 | ' background-image: url(image@1x.png);\n' + 35 | '}\n' + 36 | '\n' + 37 | '@media (min-resolution: 2dppx) {\n' + 38 | ' .image {\n' + 39 | ' background-image: url(image@2x.png);\n' + 40 | ' }\n' + 41 | '}'; 42 | 43 | sourceMapOutput = ` 44 | /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvZGlzdC9wb3N0Y3NzL2NvbXBvbmVudC5kaXN0LmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtJQUNJLFdBQVc7QUFDZjs7QUFGQTtJQUNJLFdBQVc7QUFDZjs7QUFFQTtJQUNJLG1DQUFtQztBQUN2Qzs7QUFFQTtJQUNJO1FBQ0ksbUNBQW1DO0lBQ3ZDO0FBQ0oiLCJmaWxlIjoidGVzdC9kaXN0L3Bvc3Rjc3MvY29tcG9uZW50LmRpc3QuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiOjpwbGFjZWhvbGRlciB7XG4gICAgY29sb3I6IGdyYXk7XG59XG5cbi5pbWFnZSB7XG4gICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKGltYWdlQDF4LnBuZyk7XG59XG5cbkBtZWRpYSAobWluLXJlc29sdXRpb246IDJkcHB4KSB7XG4gICAgLmltYWdlIHtcbiAgICAgICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKGltYWdlQDJ4LnBuZyk7XG4gICAgfVxufSJdfQ== */`; 45 | 46 | describe('Test utils/renderPostcss.js', () => { 47 | it(`Postcss render autoprefix plugin`, async () => { 48 | config.postcss.failOnError = false; 49 | console.log = jest.fn(); 50 | await renderPostcss(inputContent, outFile, config, (r) => { 51 | expect(r.css).toBe(outputContent); 52 | }); 53 | }); 54 | it(`Postcss render autoprefix plugin with source maps`, async () => { 55 | config.postcss.failOnError = false; 56 | config.general.isProduction = false; 57 | console.log = jest.fn(); 58 | await renderPostcss({...inputContent, map:''}, outFile, config, (r) => { 59 | expect(r.css).toBe(outputContent + sourceMapOutput); 60 | }); 61 | // return config 62 | config.general.isProduction = true; 63 | 64 | }); 65 | 66 | it(`Postcss should return errors by loggin when failOnError is false`, async () => { 67 | config.postcss.failOnError = false; 68 | console.error = jest.fn(); 69 | console.log = jest.fn(); 70 | await renderPostcss('s', 's', config, (r) =>r); 71 | expect((console.error.mock.calls[0] || console.log.mock.calls[0]).length).toBeGreaterThan(0); 72 | }); 73 | 74 | it(`Should Should exit 1 with erros failOnError`, async () => { 75 | const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); 76 | config.postcss.failOnError = true; 77 | await renderPostcss(false, false, config); 78 | expect(mockExit).toHaveBeenCalledWith(1); 79 | }); 80 | }) 81 | -------------------------------------------------------------------------------- /config/general.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const getArgumentValue = require('../utils/getArgumentValue'); 3 | /* 4 | 5 | paths configurations 6 | 7 | */ 8 | // Project key for prefix and folders 9 | const projectKey = 'myproj'; 10 | 11 | // root path of the package 12 | const rootPath = path.resolve('.'); 13 | 14 | // source files path 15 | const sourcesPath = path.join(rootPath, 'src'); 16 | 17 | // common folder for alias like import 'commons/utils/myFn'; 18 | const common = path.join(sourcesPath, 'common'); 19 | 20 | // paths that should be excluded from any search (defaults ones) 21 | const ignore = ['!(**/target/**)', '!(**/jcr_root/**)', '!(**/common/**)']; 22 | 23 | // where files should be compiled at 24 | const destinationPath = path.join(rootPath, 'dist'); 25 | 26 | // Node modules for alias lower case for 27 | const nodeModules = path.join(rootPath, 'node_modules'); 28 | 29 | /* 30 | 31 | files entries configurations 32 | 33 | */ 34 | // what is the source files key suffix to compile 35 | const sourceKey = 'source'; 36 | 37 | // what is the compiled bundle key 38 | const bundleKey = 'bundle'; 39 | 40 | // source file types ['js', 'scss'] 41 | const sourceTypes = ['js', 'scss']; 42 | 43 | // project local configurations for sub folders so it compile as a bundle 44 | const extendConfigurations = '.febuild'; 45 | 46 | // default tasks to run 47 | // optional clientLibs 48 | const defaultTasks = ['styles', 'webpack', 'clientlibs']; 49 | 50 | /* 51 | 52 | command line arguments configurations 53 | 54 | */ 55 | // By default its production, then use the flag or node env to change it 56 | const isProduction = !(process.env.NODE_ENV === 'development') 57 | && !getArgumentValue('--development'); 58 | 59 | // Mode is string format 60 | const mode = isProduction ? 'production' : 'development'; 61 | 62 | // check watchers flag 63 | const watch = getArgumentValue('--watch'); 64 | 65 | // bundle analyze flag 66 | const analyze = getArgumentValue('--analyze'); 67 | 68 | // task to execute via command line 69 | const task = getArgumentValue('--task='); 70 | 71 | // select config path params command line 72 | const configFile = getArgumentValue('--config-file='); 73 | 74 | // quiet 75 | const quiet = getArgumentValue('--quiet'); 76 | 77 | // analyzerPort 78 | const analyzerPort = getArgumentValue('--port=') || 8888; 79 | 80 | // disable Stylelint 81 | const disableStyleLint = getArgumentValue('--disable-styelint'); 82 | 83 | // general webpack 84 | const devtool = isProduction ? false : 'inline-source-map'; 85 | 86 | // general optimization 87 | const excludedFromVendors = ['babel', 'core-js']; 88 | 89 | // modules to run on webpack rules (each config module.config.js) 90 | const modules = ['babel']; 91 | 92 | // split all entries 93 | const multiple = true; 94 | 95 | // export as a hole object 96 | module.exports = { 97 | projectKey, 98 | sourceKey, 99 | bundleKey, 100 | sourceTypes, 101 | rootPath, 102 | sourcesPath, 103 | common, 104 | ignore, 105 | destinationPath, 106 | isProduction, 107 | mode, 108 | watch, 109 | analyze, 110 | analyzerPort, 111 | disableStyleLint, 112 | task, 113 | quiet, 114 | configFile, 115 | extendConfigurations, 116 | modules, 117 | multiple, 118 | nodeModules, 119 | defaultTasks, 120 | devtool, 121 | excludedFromVendors 122 | }; 123 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Cognizant Netcentric Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at Grp-opensourceoffice@adobe.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: https://contributor-covenant.org 74 | [version]: https://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /docs/migration.md: -------------------------------------------------------------------------------- 1 | 2 | # Migrating to v4.0.0 3 | 4 | This release contains breaking changes. We know these can be disruptive, but they were needed to keep the dependencies updated. 5 | 6 | ### using [Node 17+](https://nodejs.org/download/release/v18.18.2/) 7 | breaking changes may also affect you: 8 | - webpack output uses [crypto.createHash that utilize MD4 of a OpenSSL 3.0 Legacy provider]() 9 | 10 | Those You will see this error: 11 | ```bash 12 | [webpack-cli] Error: error:0308010C:digital envelope routines::unsupported 13 | at new Hash (node:internal/crypto/hash:67:19) 14 | ``` 15 | 16 | #### Workaround one: 17 | ```javascript 18 | 19 | // uses a new hash function 20 | // at output.config or at .febuild output property 21 | { 22 | hashFunction: "xxhash64" 23 | } 24 | ``` 25 | #### Workaround two: 26 | ```bash 27 | # At your terminal, profile or before your build function run / use 28 | export NODE_OPTIONS=--openssl-legacy-provider; 29 | ``` 30 | 31 | 32 | ### [ESlint ^8](https://eslint.org/docs/latest/use/migrate-to-8.0.0) 33 | Build now uses [eslint-webpack-plugin](https://www.npmjs.com/package/eslint-webpack-plugin) intead of deprecated [eslint-loader](https://www.npmjs.com/package/eslint-loader) 34 | It's setting the plugin now instead of loader then 35 | #### breaking changes 36 | - review your .febuild exported eslint property to use only the [eslint options schema](https://eslint.org/docs/latest/integrate/nodejs-api#-new-eslintoptions) 37 | 38 | 39 | 40 | ### [Stylelint ^15](https://stylelint.io/migration-guide/to-15/) 41 | 42 | We recommend extending a shared configuration like [@netcentric/stylelint-config](https://github.com/Netcentric/stylelint-config) that includes the appropriate syntax to lint Scss. 43 | 44 | Three breaking changes may also affect you: 45 | - Upgrade [@netcentric/stylelint-config](https://github.com/Netcentric/stylelint-config) to "^2.0.0", 46 | - removed processors configuration property 47 | - removed support for Node.js 12 48 | - changed overrides.extends behavior 49 | 50 | ### [Webpack ^5](https://webpack.js.org/migrate/5/) and [babel v7](https://babeljs.io/docs/v7-migration) 51 | 52 | Please review your webpack config options accordenly 53 | - Review package.json dependecies on your project to match current one 54 | - review babel configs to respect [babel v7](https://babeljs.io/docs/v7-migration) 55 | - review your .febuild exported properties to use webpack latest options 56 | - [stats](https://webpack.js.org/configuration/stats/) 57 | - [cache](https://webpack.js.org/configuration/cache/) 58 | - [devServer](https://webpack.js.org/configuration/dev-server/) 59 | - [performance](https://webpack.js.org/configuration/performance/) 60 | - [resolve](https://webpack.js.org/configuration/resolve/) 61 | - [optimization](https://webpack.js.org/configuration/optimization/) 62 | - [plugins](https://webpack.js.org/configuration/plugins/) 63 | 64 | 65 | 66 | # Migrating to v2.0.0 67 | 68 | This release contains breaking changes. We know these can be disruptive, but they were needed to keep the dependencies updated. 69 | 70 | [Stylelint v14](https://stylelint.io/migration-guide/to-14/) does not longer includes the syntaxes that parse CSS-like languages like Scss. You will need to install and configure these syntaxes in your project. We recommend extending a shared configuration like [@netcentric/stylelint-config](https://github.com/Netcentric/stylelint-config) that includes the appropriate syntax to lint Scss. 71 | 72 | First, install the shared configuration as a dependency: 73 | 74 | ```bash 75 | npm install --save-dev @netcentric/stylelint-config 76 | ``` 77 | 78 | Then, update your [Stylelint configuration object](https://stylelint.io/user-guide/configure/) to use it: 79 | 80 | ```json 81 | { 82 | "extends": "@netcentric/stylelint-config", 83 | "rules": { 84 | ... 85 | } 86 | } 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration file 2 | 3 | The default configuration can be extended via a `.febuild` file. 4 | This configuration file is loaded and executed as a JavaScript module. 5 | It is used for all files located in the same directory as the `.febuild` file and in the subdirectory tree. 6 | 7 | You can add multiple `.febuild` whenever you need to run a separate build with other options. 8 | 9 | These are the configurations that can be extended: 10 | 11 | - general 12 | - output 13 | - [optimization](https://webpack.js.org/configuration/optimization/) 14 | - [plugins](https://webpack.js.org/configuration/plugins/) 15 | - eslint 16 | - babel 17 | - sass 18 | - clientlibs 19 | - stylelint 20 | - [resolve](https://webpack.js.org/configuration/resolve/) 21 | - postcss 22 | - templates 23 | 24 | Other webpack options 25 | 26 | - [stats](https://webpack.js.org/configuration/stats/) 27 | - [cache](https://webpack.js.org/configuration/cache/) 28 | - [devServer](https://webpack.js.org/configuration/dev-server/) 29 | - [performance](https://webpack.js.org/configuration/performance/) 30 | 31 | ## Overriding a Default Configuration 32 | 33 | To override a configuration, add a new entry in the object exported in your `.febuild` with the name of the configuration you want to override, which value is an object whith entries that matches the existing options you want to override. 34 | 35 | E.g., to override the default Babel configuration with new `exclude` paths and plugins, you can do the following in your `.febuild` file: 36 | 37 | ```javascript 38 | module.exports = { 39 | babel: { 40 | exclude: /node_modules\/(?!swiper|dom7)/, 41 | use: { 42 | options: { 43 | plugins: ['@babel/plugin-proposal-optional-chaining', '@babel/plugin-transform-runtime', '@babel/plugin-proposal-object-rest-spread'] 44 | } 45 | } 46 | } 47 | }; 48 | ``` 49 | 50 | Default configuration can be extended by using a function instead of an object, which accepts an argument that is the default configuration. 51 | 52 | ```javascript 53 | module.exports = (defaultConfig) => ({ 54 | babel: { 55 | use: { 56 | options: { 57 | plugins: ['@babel/plugin-proposal-optional-chaining', ...defaultConfig.babel.use.options] 58 | } 59 | } 60 | } 61 | }); 62 | ``` 63 | 64 | ## Configurations 65 | 66 | ### General 67 | 68 | This configuration part is used for basic project setup. You will find an explanation of each key as a comment next to it. 69 | Defaults: 70 | 71 | ```javascript 72 | module.exports = { 73 | general: { 74 | // Your project name with which ClientLibs category are prefixed 75 | projectKey: "myproj", 76 | // Only the source files with this suffix will be compiled 77 | sourceKey: "source", 78 | // The compiled bundle filename suffix 79 | bundleKey: "bundle", 80 | // The path to the directory with your source files 81 | sourcesPath: "src", 82 | // Path to the dir with the code shared among Scss and JS files 83 | common: "common", 84 | // Paths to ignore when the build looks for files to compile 85 | ignore: ["!(**/target/**)", "!(**/jcr_root/**)", "!(**/common/**)"], 86 | // Destination of the compiled files 87 | destinationPath: "dist", 88 | // Name of the configuration file 89 | extendConfigurations: ".febuild", 90 | // Modules to run with webpack, each being a config file (e.g. eslint.config.js) 91 | modules: [ "eslint", "babel" ], 92 | // The tasks to run when executing `npm run build` 93 | defaultTasks: [ "styles", "webpack", "clientlibs" ], 94 | } 95 | } 96 | ``` 97 | 98 | Example of overriding the `defaultTasks` to exclude the 'clientlibs' task: 99 | 100 | ```javascript 101 | module.exports = { 102 | general: { 103 | defaultTasks: ['styles', 'webpack'] 104 | } 105 | } 106 | ``` 107 | 108 | ### Babel 109 | 110 | Babel webpack plugin, enabled by default in the option `general.modules`. 111 | For more information about the configuration options check [babel-loader](https://github.com/babel/babel-loader). 112 | 113 | ```javascript 114 | { 115 | babel: { 116 | enforce: 'post', 117 | test: /\.js$/, 118 | exclude: /node_modules\/(?!@netcentric)/, 119 | use: { 120 | loader: 'babel-loader', 121 | options: { 122 | presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }]], 123 | plugins: ['@babel/plugin-transform-runtime', '@babel/plugin-proposal-object-rest-spread'] 124 | } 125 | } 126 | } 127 | } 128 | ``` 129 | 130 | #### @babel/preset-env 131 | 132 | By default `@babel/preset-env` will use [`browserslist` config sources](https://github.com/browserslist/browserslist) _unless_ either the `targets` or `ignoreBrowserslistConfig` options are set. 133 | 134 | > When no browser targets are specified, Babel will assume the oldest browsers possible, which will increase the output code size. 135 | 136 | We recommend using a [.browserslistrc](https://github.com/browserslist/browserslist#browserslistrc) file to specify the targets. 137 | 138 | **`useBuiltIns: usage`** 139 | 140 | This option configures how Babel [handles the polyfills](https://babeljs.io/docs/en/babel-preset-env#usebuiltins), by adding the specific imports only when the polyfill is used in each file. 141 | 142 | What is the advantages over "entry"? 143 | - It allows proper tree-shaking the polyfills 144 | - Reduce the size of the JavaScript file entrypoint 145 | 146 | #### Core JS 3 147 | 148 | [core-js 3](https://github.com/zloirock/core-js) is included by default, and set in the options of `@babel/preset-env` to import only the polyfills used in your code. 149 | 150 | Hence **you don't need to import core-js in your project**, or code duplication will happen increasing the size of the output code. 151 | 152 | ### Stylelint 153 | 154 | Stylelint is a CSS linter which can also lint Scss files. 155 | 156 | Default configuration: 157 | 158 | ```javascript 159 | module.exports = { 160 | { 161 | stylelint: { 162 | // Stops the build if a linter error is found 163 | failOnError: true 164 | } 165 | } 166 | } 167 | ``` 168 | 169 | Please note that you need a [Stylelint configuration object]((https://stylelint.io/user-guide/configure/)) to parse CSS-like languages like Scss. We recommend extending a shared configuration like [@netcentric/stylelint-config](https://github.com/Netcentric/stylelint-config). 170 | 171 | You can add your own linter rules in the [Stylelint configuration object](https://stylelint.io/user-guide/configure/#rules). 172 | 173 | ### ESlint 174 | 175 | ESLint statically analyzes your code to quickly find problems. 176 | 177 | For more information about the configuration options check [eslint-loader](https://github.com/webpack-contrib/eslint-loader). 178 | 179 | ```javascript 180 | module.exports = { 181 | failOnError: true, 182 | fix: true // deprecated with scss only for css 183 | } 184 | ``` 185 | 186 | ### Sass 187 | 188 | [Sass](https://sass-lang.com/) is the default CSS preprocessor supported by the fe-build. 189 | 190 | ```javascript 191 | { 192 | sass: { 193 | // Paths where Sass will look for stylesheets (when using @import and @use) 194 | includePaths: [path.join(common, 'styles'), nodeModules], 195 | // The output style of the compiled CSS: "expanded, compressed, nested or compact 196 | outputStyle: isProduction ? 'compressed' : 'expanded' 197 | } 198 | } 199 | ``` 200 | 201 | ### PostCSS 202 | 203 | [PostCSS](https://postcss.org/) is used to transform the CSS code generated after the Sass compilation. 204 | 205 | Default configuration: 206 | 207 | ```javascript 208 | { 209 | postcss: { 210 | // Default plugins 211 | plugins: ['autoprefixer', ['another-postcss-plugin',{ foo: 'bar'}]], 212 | // Stops the build if an error is found 213 | failOnError: true 214 | } 215 | } 216 | ``` 217 | 218 | You can add new PostCSS plugins by overriding the option `plugins` in your `.febuild` file. Place them _before_ the autoprefixer plugin. 219 | 220 | ```javascript 221 | postcss: { 222 | plugins: ['mypostcssplugin', 'autoprefixer'] 223 | } 224 | ``` 225 | 226 | ### Plugins 227 | 228 | This configuration part refers to [Webpack plugins](https://webpack.js.org/plugins/define-plugin/). 229 | 230 | Default configuration: 231 | 232 | ```javascript 233 | { 234 | plugins: [ 235 | new webpack.DefinePlugin({ 236 | 'process.env.NODE_ENV': JSON.stringify(mode) 237 | } 238 | ] 239 | } 240 | ``` 241 | 242 | You can add new plugins by overriding the option `plugins` or pushing them into the default configuration. 243 | 244 | ```javascript 245 | module.exports = (defaultConfig) => ({ 246 | plugins: [...defaultConfig.plugins, myPlugin] 247 | }); 248 | ``` 249 | 250 | ### Treeshaking, Commons and Vendors 251 | 252 | Webpack optimizations for your JavaScript code: minimization, code splitting and tree shaking. 253 | Default configuration: 254 | 255 | ```javascript 256 | { 257 | optimization: { 258 | minimize: isProduction, 259 | usedExports: isProduction, 260 | runtimeChunk: { 261 | name: 'commons/treeshaking.bundle.js' 262 | }, 263 | splitChunks: { 264 | cacheGroups: { 265 | // this treeshake vendors (but keep unique vendors at the clientlibs it belongs ) 266 | vendors: { 267 | test: mod => moduleIsVendor(mod.context, excludedFromVendors), 268 | name: 'commons/vendors.bundle.js', 269 | chunks: 'all', 270 | minChunks: 2 271 | }, 272 | // this treeshakes common imports, if are and more than 2 clientlibs 273 | treeshaking: { 274 | test: mod => !moduleIsVendor(mod.context, excludedFromVendors), 275 | name: 'commons/treeshaking.bundle.js', 276 | chunks: 'all', 277 | minChunks: 2 278 | } 279 | } 280 | } 281 | } 282 | } 283 | ``` 284 | 285 | If a module is imported into more than 2 files, it's extracted to a common file. 286 | There are 2 main common files: 287 | 288 | - `treeshaking.bundle.js`: common code that reside in the project. 289 | - `vendors.bundle.js`: common code that is outside of the scope your project. 290 | 291 | **vendors.bundle.js** 292 | 293 | This is intended to extract reused third-party scripts that are imported in two or more modules to a different file, so it avoids duplication and this file can be loaded separately. 294 | Also it is good to separate those third-party from the regular tree shaking since this vendors sometimes might be best suited as an external option. 295 | 296 | Advantages of having a separated vendor: 297 | - Clear view of the impact of third-party on your code base. 298 | - You can identify possible additions to externals (removing it completely from your code). 299 | 300 | **treeshaking.bundle.js** 301 | 302 | This is intended to optimize the codebase of the project, by code splitting the modules that are the building blocks of it, like core-js, babel and @your modules. 303 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Cognizant Netcentric 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [5.3.1](https://github.com/Netcentric/fe-build/compare/v5.3.0...v5.3.1) (2025-05-15) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **utils:** Fix incorrect metadata content for postcss extraEntries ([d524dce](https://github.com/Netcentric/fe-build/commit/d524dceb44799a3f92af75d06b2b453019c82993)) 7 | 8 | # [5.3.0](https://github.com/Netcentric/fe-build/compare/v5.2.0...v5.3.0) (2025-05-09) 9 | 10 | 11 | ### Features 12 | 13 | * **config:** Support clientlib metadata generation for dynamically created CSS files ([81a7229](https://github.com/Netcentric/fe-build/commit/81a7229757568abea848f9aaddb8eb055445eaf2)) 14 | 15 | # [5.2.0](https://github.com/Netcentric/fe-build/compare/v5.1.3...v5.2.0) (2025-05-05) 16 | 17 | 18 | ### Features 19 | 20 | * **utils:** support plugins with configuration objects ([3eeeec8](https://github.com/Netcentric/fe-build/commit/3eeeec818fb8cf49d228259b8e5eda1aec11a1be)) 21 | 22 | ## [5.1.3](https://github.com/Netcentric/fe-build/compare/v5.1.2...v5.1.3) (2025-04-16) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * [#112](https://github.com/Netcentric/fe-build/issues/112) - update jest timeout for webpack ([a34e790](https://github.com/Netcentric/fe-build/commit/a34e7903115958c224366ee48a876c92028c2d77)) 28 | * [#112](https://github.com/Netcentric/fe-build/issues/112) - update source maps for new sass version ([2ba91fd](https://github.com/Netcentric/fe-build/commit/2ba91fd88c44a5b78bcff1a71d6fce5f59377084)) 29 | 30 | ## [5.1.2](https://github.com/Netcentric/fe-build/compare/v5.1.1...v5.1.2) (2025-04-10) 31 | 32 | 33 | ### Bug Fixes 34 | 35 | * **utils:** Fix category name generation to prevent consecutive dots and leading dots ([8b85be7](https://github.com/Netcentric/fe-build/commit/8b85be7dee5f1af519d6d2962da25baae4d270a4)) 36 | 37 | ## [5.1.1](https://github.com/Netcentric/fe-build/compare/v5.1.0...v5.1.1) (2025-03-13) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * **config:** change package namespace in babel.config exclude property ([f229c9e](https://github.com/Netcentric/fe-build/commit/f229c9e32bbf09d1492dd55a9e7fa2a857eef4c0)) 43 | 44 | # [5.1.0](https://github.com/Netcentric/fe-build/compare/v5.0.0...v5.1.0) (2025-03-13) 45 | 46 | 47 | ### Features 48 | 49 | * ([#112](https://github.com/Netcentric/fe-build/issues/112)) update legacy-js-api, ([#69](https://github.com/Netcentric/fe-build/issues/69)) add option of .febuild.js, fix [#19](https://github.com/Netcentric/fe-build/issues/19) ([4624297](https://github.com/Netcentric/fe-build/commit/4624297b5d8df680076cceaf68e03a6175a39f05)) 50 | 51 | # [5.0.0](https://github.com/Netcentric/fe-build/compare/v4.0.2...v5.0.0) (2025-01-29) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * fix tests ([3f74d3e](https://github.com/Netcentric/fe-build/commit/3f74d3eea4ddab51df1bc9a4ca098a64d0896b23)) 57 | * update dependencies ([2108e53](https://github.com/Netcentric/fe-build/commit/2108e532eb6aaec807b4180ffa1c1da712ee0b02)) 58 | 59 | 60 | * Merge pull request #114 from Netcentric/feature/update-stylelint ([179ca94](https://github.com/Netcentric/fe-build/commit/179ca9492e70cefba622e5cd8a03cb1ee0d35971)), closes [#114](https://github.com/Netcentric/fe-build/issues/114) 61 | 62 | 63 | ### BREAKING CHANGES 64 | 65 | * Update stylelint to v16 66 | * Update stylelint to v16 67 | 68 | ## [4.0.2](https://github.com/Netcentric/fe-build/compare/v4.0.1...v4.0.2) (2024-12-10) 69 | 70 | 71 | ### Bug Fixes 72 | 73 | * override semver ([2358226](https://github.com/Netcentric/fe-build/commit/2358226aa1f3f652fdd011b081d0aa3fba8de187)) 74 | * update dependencies ([93520ff](https://github.com/Netcentric/fe-build/commit/93520ff8ef5538a6927650806323ee7488a5745e)) 75 | 76 | ## [4.0.1](https://github.com/Netcentric/fe-build/compare/v4.0.0...v4.0.1) (2024-01-31) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * Add webpack tests ([aa972bc](https://github.com/Netcentric/fe-build/commit/aa972bca064553cf4bda978b143d2479a6e03241)) 82 | * clone arrays in merge utils ([5c432f8](https://github.com/Netcentric/fe-build/commit/5c432f823455403872b1327ae50210741f53d259)) 83 | 84 | # [4.0.0](https://github.com/Netcentric/fe-build/compare/v3.1.0...v4.0.0) (2023-10-19) 85 | 86 | 87 | * BREAKING CHANGE: Trigger Manual Release ([4873a27](https://github.com/Netcentric/fe-build/commit/4873a279c488fb0a7689e6676a9d494c7dc022d3)) 88 | 89 | 90 | ### BREAKING CHANGES 91 | 92 | * Forced Manual Release without code changes 93 | 94 | # [3.1.0](https://github.com/Netcentric/fe-build/compare/v3.0.4...v3.1.0) (2023-10-19) 95 | 96 | 97 | ### Bug Fixes 98 | 99 | * update stylelint ([ddec6b5](https://github.com/Netcentric/fe-build/commit/ddec6b5464c9cda2e5e5460d286bc069516fcb40)) 100 | 101 | 102 | ### Features 103 | 104 | * **utils:** upgrade to webpack 5 ([81d28ca](https://github.com/Netcentric/fe-build/commit/81d28ca56595074e8f15e3eb2046c3e27ccae877)) 105 | 106 | ## [3.0.4](https://github.com/Netcentric/fe-build/compare/v3.0.3...v3.0.4) (2023-10-09) 107 | 108 | 109 | ### Bug Fixes 110 | 111 | * **contribution:** fix preinstall script that was only required to contribution and break npm i windows install ([5ca1c2a](https://github.com/Netcentric/fe-build/commit/5ca1c2a20992c651146379f967c736ac60848989)) 112 | 113 | ## [3.0.2](https://github.com/Netcentric/fe-build/compare/v3.0.1...v3.0.2) (2023-01-25) 114 | 115 | 116 | ### Bug Fixes 117 | 118 | * change preinstall script to account for missing .git/hooks folders ([07e8362](https://github.com/Netcentric/fe-build/commit/07e83627a1bc1026261315360da9c9e4a2e851a4)) 119 | 120 | ## [3.0.1](https://github.com/Netcentric/fe-build/compare/v3.0.0...v3.0.1) (2022-10-21) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * **hooks:** [#82](https://github.com/Netcentric/fe-build/issues/82) add hooks to package files ([0ef08ac](https://github.com/Netcentric/fe-build/commit/0ef08aca3ce45616160e56a27ffdcd598d94ff79)) 126 | 127 | # [3.0.0](https://github.com/Netcentric/fe-build/compare/v2.1.0...v3.0.0) (2022-08-25) 128 | 129 | 130 | ### Bug Fixes 131 | 132 | * Changes values defined in `SourceMapOptions` to match PostCSS v8 API ([bf0344d](https://github.com/Netcentric/fe-build/commit/bf0344dd4f1a513e7327ccf11c147da7d9216703)) 133 | * Move gaze to regular depedencies ([d2e71af](https://github.com/Netcentric/fe-build/commit/d2e71af7c0b35d4d1d6fd6bd7ec11eb8bece8896)), closes [#65](https://github.com/Netcentric/fe-build/issues/65) 134 | * Removes IE Mobile from browserlist ([389bbd4](https://github.com/Netcentric/fe-build/commit/389bbd4f625ba40e08f39853831f97cc03180b7c)) 135 | * update missing changelog plugin version ([a58f949](https://github.com/Netcentric/fe-build/commit/a58f94932b233bad8d5f26b18a844fc7c90f490b)) 136 | * Updates eslint-plugin-import because current version is not supported by eslint v7 ([34c4d37](https://github.com/Netcentric/fe-build/commit/34c4d373206a7650ae61a367fbc57ee79147a192)) 137 | * **utils:** adjust custom missing destination basePath when custom sourcesPath defined ([9cd6d0f](https://github.com/Netcentric/fe-build/commit/9cd6d0fbbe3e9c28ea2b3a81ccb303f08d6c316c)) 138 | 139 | 140 | ### Feat 141 | 142 | * Bump major version number ([d06c2d4](https://github.com/Netcentric/fe-build/commit/d06c2d44da0018c76ab16891237eb2f870a5ce94)) 143 | 144 | 145 | ### Features 146 | 147 | * Adds migration guide ([59e987d](https://github.com/Netcentric/fe-build/commit/59e987d93291e0e5add95227aee829a5b58df954)) 148 | * Adds supported node.js versions ([30ee448](https://github.com/Netcentric/fe-build/commit/30ee448973467e779bb2c6fde7019e71270fd71e)) 149 | * Updates stylelint to v14.6.1 and removes obsolete `syntax` option ([930dc8e](https://github.com/Netcentric/fe-build/commit/930dc8e319dd3ebc7b2cf970f1c37190fcbcaf0d)) 150 | 151 | 152 | ### BREAKING CHANGES 153 | 154 | * Stylelint v14 does not include syntaxes by default 155 | 156 | Stylelint no longer includes the syntaxes to parse CSS-like languages like SCSS. 157 | Migration guide: 158 | - In your Stylelint configuration object extend a shared config like @netcentric/stylelint-config 159 | 160 | # [2.1.0](https://github.com/Netcentric/fe-build/compare/v2.0.2...v2.1.0) (2022-08-18) 161 | 162 | 163 | ### Features 164 | 165 | * make Stylelint optional ([d344705](https://github.com/Netcentric/fe-build/commit/d34470560114b034f919c29ffb258710810d02a6)) 166 | 167 | ## [2.0.2](https://github.com/netcentric/fe-build/compare/v2.0.1...v2.0.2) (2022-07-18) 168 | 169 | 170 | ### Bug Fixes 171 | 172 | * **utils:** adjust custom missing destination basePath when custom sourcesPath defined ([97ed7d4](https://github.com/netcentric/fe-build/commit/97ed7d4de64da146866c625a1d9cabad26099129)) 173 | 174 | ## [2.0.1](https://github.com/netcentric/fe-build/compare/v2.0.0...v2.0.1) (2022-07-12) 175 | 176 | 177 | ### Bug Fixes 178 | 179 | * Move gaze to regular depedencies ([165c90b](https://github.com/netcentric/fe-build/commit/165c90b4665c941b3a223d7ec2b75bb380147910)), closes [#65](https://github.com/netcentric/fe-build/issues/65) 180 | 181 | # [2.0.0](https://github.com/netcentric/fe-build/compare/v1.2.0...v2.0.0) (2022-05-05) 182 | 183 | 184 | ### Bug Fixes 185 | 186 | * Removes IE Mobile from browserlist ([344d97e](https://github.com/netcentric/fe-build/commit/344d97e3e1af3839b1a2d30b742006ab998b87c3)) 187 | * Updates eslint-plugin-import because current version is not supported by eslint v7 ([2b6aeff](https://github.com/netcentric/fe-build/commit/2b6aeff713840c97a3fdb1c2f67b0911ab01575e)) 188 | 189 | 190 | ### Feat 191 | 192 | * Bump major version number ([c8ca422](https://github.com/netcentric/fe-build/commit/c8ca42259f659626a65ca55568262563dfb7f968)) 193 | 194 | 195 | ### Features 196 | 197 | * Adds migration guide ([e537ab1](https://github.com/netcentric/fe-build/commit/e537ab1f2ac12ecfb370459af66f98d9b4a38576)) 198 | * Adds supported node.js versions ([31e9371](https://github.com/netcentric/fe-build/commit/31e93719b3c6263b42bed86545e8a70782fb77f0)) 199 | * Updates stylelint to v14.6.1 and removes obsolete `syntax` option ([18742aa](https://github.com/netcentric/fe-build/commit/18742aadba82e5b83c7995e6a5c6b145101bf490)) 200 | 201 | 202 | ### BREAKING CHANGES 203 | 204 | * Stylelint v14 does not include syntaxes by default 205 | 206 | Stylelint no longer includes the syntaxes to parse CSS-like languages like Scss. 207 | Migration guide: 208 | - In your Stylelint configuration object extend a shared config like @netcentric/stylelint-config 209 | 210 | # [1.2.0](https://github.com/netcentric/fe-build/compare/v1.1.3...v1.2.0) (2022-05-05) 211 | 212 | 213 | ### Features 214 | 215 | * replaces hardcoded path separator with `path.sep`in order to make the build work in any OS ([a4f58a8](https://github.com/netcentric/fe-build/commit/a4f58a84f05abad4e6692a1167959b7cedaf16e4)) 216 | 217 | ## [1.1.3](https://github.com/netcentric/fe-build/compare/v1.1.2...v1.1.3) (2022-04-24) 218 | 219 | 220 | ### Bug Fixes 221 | 222 | * Changes values defined in `SourceMapOptions` to match PostCSS v8 API ([d65132f](https://github.com/netcentric/fe-build/commit/d65132fb69b781c643ae5761f9ae5cc415025b9c)) 223 | 224 | ## [1.1.2](https://github.com/netcentric/fe-build/compare/v1.1.1...v1.1.2) (2022-02-14) 225 | 226 | 227 | ### Bug Fixes 228 | 229 | * change cli path ([ab31b00](https://github.com/netcentric/fe-build/commit/ab31b00266cd71c7b2f5a38778767a083aea9ad7)) 230 | * update missing changelog plugin version ([151c1ea](https://github.com/netcentric/fe-build/commit/151c1ea2c90308ffdb0cabeb35e8d15a568932af)) 231 | 232 | ## [1.1.1](https://github.com/netcentric/fe-build/compare/v1.1.0...v1.1.1) (2022-02-14) 233 | 234 | 235 | ### Bug Fixes 236 | 237 | * include index.js ([81cef07](https://github.com/netcentric/fe-build/commit/81cef07e3e38aa1a20dab0da51e651c1e0b85c24)) 238 | 239 | # [1.1.0](https://github.com/netcentric/fe-build/compare/v1.0.2...v1.1.0) (2022-02-13) 240 | 241 | 242 | ### Bug Fixes 243 | 244 | * move to node v16, update package-lock with v2 ([fd1fafc](https://github.com/netcentric/fe-build/commit/fd1fafc37c6e8372002314a74ccc47d3eae846e5)) 245 | * update actions for node v16 ([7c21f6c](https://github.com/netcentric/fe-build/commit/7c21f6c7d8cd7bc1c71da3f42ade5bd3053a8f59)) 246 | * update eslint ([14698c0](https://github.com/netcentric/fe-build/commit/14698c019455d926ff249e546051d220a4f14acc)) 247 | 248 | 249 | ### Features 250 | 251 | * fix dependencies versions ([18a5c9d](https://github.com/netcentric/fe-build/commit/18a5c9d948e3f9f1b20b9b77c815b81f72f43c84)) 252 | 253 | ## [1.0.2](https://github.com/netcentric/fe-build/compare/v1.0.1...v1.0.2) (2021-07-09) 254 | 255 | 256 | ### Bug Fixes 257 | 258 | * add package.json to automated release updates ([7c1515e](https://github.com/netcentric/fe-build/commit/7c1515e9a2dce6be182aca0333335e64350ac859)) 259 | 260 | ## [1.0.1](https://github.com/netcentric/fe-build/compare/v1.0.0...v1.0.1) (2021-05-26) 261 | 262 | 263 | ### Bug Fixes 264 | 265 | * dist path replace ([51d720c](https://github.com/netcentric/fe-build/commit/51d720ce0b80c6a8d77b64b248a47e2367e8d4d5)) 266 | 267 | # 1.0.0 (2021-05-21) 268 | 269 | 270 | ### Bug Fixes 271 | 272 | * remove broken image ([db009d1](https://github.com/netcentric/fe-build/commit/db009d1582d952f848fc79bec5df2072a78b2b73)) 273 | * **init:** initial commit ([42cf70a](https://github.com/netcentric/fe-build/commit/42cf70af16415202ad6af297ee456d560d2b214a)) 274 | * **init:** NPM_TOKEN secret info added ([5616040](https://github.com/netcentric/fe-build/commit/56160408295b486eabd14dcb81461f98231d492c)) 275 | --------------------------------------------------------------------------------