UNPKG

webpack

Version:

Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

568 lines (516 loc) 12.1 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ /* <rules>: <rule> <rules>: [<rule>] <rule>: { resource: { test: <condition>, include: <condition>, exclude: <condition>, }, resource: <condition>, -> resource.test test: <condition>, -> resource.test include: <condition>, -> resource.include exclude: <condition>, -> resource.exclude resourceQuery: <condition>, compiler: <condition>, issuer: <condition>, use: "loader", -> use[0].loader loader: <>, -> use[0].loader loaders: <>, -> use options: {}, -> use[0].options, query: {}, -> options parser: {}, use: [ "loader" -> use[x].loader ], use: [ { loader: "loader", options: {} } ], rules: [ <rule> ], oneOf: [ <rule> ] } <condition>: /regExp/ <condition>: function(arg) {} <condition>: "starting" <condition>: [<condition>] // or <condition>: { and: [<condition>] } <condition>: { or: [<condition>] } <condition>: { not: [<condition>] } <condition>: { test: <condition>, include: <condition>, exclude: <condition> } normalized: { resource: function(), resourceQuery: function(), compiler: function(), issuer: function(), use: [ { loader: string, options: string, <any>: <any> } ], rules: [<rule>], oneOf: [<rule>], <any>: <any>, } */ "use strict"; const notMatcher = matcher => { return str => { return !matcher(str); }; }; const orMatcher = items => { return str => { for (let i = 0; i < items.length; i++) { if (items[i](str)) return true; } return false; }; }; const andMatcher = items => { return str => { for (let i = 0; i < items.length; i++) { if (!items[i](str)) return false; } return true; }; }; module.exports = class RuleSet { constructor(rules) { this.references = Object.create(null); this.rules = RuleSet.normalizeRules(rules, this.references, "ref-"); } static normalizeRules(rules, refs, ident) { if (Array.isArray(rules)) { return rules.map((rule, idx) => { return RuleSet.normalizeRule(rule, refs, `${ident}-${idx}`); }); } else if (rules) { return [RuleSet.normalizeRule(rules, refs, ident)]; } else { return []; } } static normalizeRule(rule, refs, ident) { if (typeof rule === "string") { return { use: [ { loader: rule } ] }; } if (!rule) { throw new Error("Unexcepted null when object was expected as rule"); } if (typeof rule !== "object") { throw new Error( "Unexcepted " + typeof rule + " when object was expected as rule (" + rule + ")" ); } const newRule = {}; let useSource; let resourceSource; let condition; const checkUseSource = newSource => { if (useSource && useSource !== newSource) { throw new Error( RuleSet.buildErrorMessage( rule, new Error( "Rule can only have one result source (provided " + newSource + " and " + useSource + ")" ) ) ); } useSource = newSource; }; const checkResourceSource = newSource => { if (resourceSource && resourceSource !== newSource) { throw new Error( RuleSet.buildErrorMessage( rule, new Error( "Rule can only have one resource source (provided " + newSource + " and " + resourceSource + ")" ) ) ); } resourceSource = newSource; }; if (rule.test || rule.include || rule.exclude) { checkResourceSource("test + include + exclude"); condition = { test: rule.test, include: rule.include, exclude: rule.exclude }; try { newRule.resource = RuleSet.normalizeCondition(condition); } catch (error) { throw new Error(RuleSet.buildErrorMessage(condition, error)); } } if (rule.resource) { checkResourceSource("resource"); try { newRule.resource = RuleSet.normalizeCondition(rule.resource); } catch (error) { throw new Error(RuleSet.buildErrorMessage(rule.resource, error)); } } if (rule.realResource) { try { newRule.realResource = RuleSet.normalizeCondition(rule.realResource); } catch (error) { throw new Error(RuleSet.buildErrorMessage(rule.realResource, error)); } } if (rule.resourceQuery) { try { newRule.resourceQuery = RuleSet.normalizeCondition(rule.resourceQuery); } catch (error) { throw new Error(RuleSet.buildErrorMessage(rule.resourceQuery, error)); } } if (rule.compiler) { try { newRule.compiler = RuleSet.normalizeCondition(rule.compiler); } catch (error) { throw new Error(RuleSet.buildErrorMessage(rule.compiler, error)); } } if (rule.issuer) { try { newRule.issuer = RuleSet.normalizeCondition(rule.issuer); } catch (error) { throw new Error(RuleSet.buildErrorMessage(rule.issuer, error)); } } if (rule.loader && rule.loaders) { throw new Error( RuleSet.buildErrorMessage( rule, new Error( "Provided loader and loaders for rule (use only one of them)" ) ) ); } const loader = rule.loaders || rule.loader; if (typeof loader === "string" && !rule.options && !rule.query) { checkUseSource("loader"); newRule.use = RuleSet.normalizeUse(loader.split("!"), ident); } else if (typeof loader === "string" && (rule.options || rule.query)) { checkUseSource("loader + options/query"); newRule.use = RuleSet.normalizeUse( { loader: loader, options: rule.options, query: rule.query }, ident ); } else if (loader && (rule.options || rule.query)) { throw new Error( RuleSet.buildErrorMessage( rule, new Error( "options/query cannot be used with loaders (use options for each array item)" ) ) ); } else if (loader) { checkUseSource("loaders"); newRule.use = RuleSet.normalizeUse(loader, ident); } else if (rule.options || rule.query) { throw new Error( RuleSet.buildErrorMessage( rule, new Error( "options/query provided without loader (use loader + options)" ) ) ); } if (rule.use) { checkUseSource("use"); newRule.use = RuleSet.normalizeUse(rule.use, ident); } if (rule.rules) { newRule.rules = RuleSet.normalizeRules( rule.rules, refs, `${ident}-rules` ); } if (rule.oneOf) { newRule.oneOf = RuleSet.normalizeRules( rule.oneOf, refs, `${ident}-oneOf` ); } const keys = Object.keys(rule).filter(key => { return ![ "resource", "resourceQuery", "compiler", "test", "include", "exclude", "issuer", "loader", "options", "query", "loaders", "use", "rules", "oneOf" ].includes(key); }); for (const key of keys) { newRule[key] = rule[key]; } if (Array.isArray(newRule.use)) { for (const item of newRule.use) { if (item.ident) { refs[item.ident] = item.options; } } } return newRule; } static buildErrorMessage(condition, error) { const conditionAsText = JSON.stringify( condition, (key, value) => { return value === undefined ? "undefined" : value; }, 2 ); return error.message + " in " + conditionAsText; } static normalizeUse(use, ident) { if (typeof use === "function") { return data => RuleSet.normalizeUse(use(data), ident); } if (Array.isArray(use)) { return use .map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`)) .reduce((arr, items) => arr.concat(items), []); } return [RuleSet.normalizeUseItem(use, ident)]; } static normalizeUseItemString(useItemString) { const idx = useItemString.indexOf("?"); if (idx >= 0) { return { loader: useItemString.substr(0, idx), options: useItemString.substr(idx + 1) }; } return { loader: useItemString, options: undefined }; } static normalizeUseItem(item, ident) { if (typeof item === "string") { return RuleSet.normalizeUseItemString(item); } const newItem = {}; if (item.options && item.query) { throw new Error("Provided options and query in use"); } if (!item.loader) { throw new Error("No loader specified"); } newItem.options = item.options || item.query; if (typeof newItem.options === "object" && newItem.options) { if (newItem.options.ident) { newItem.ident = newItem.options.ident; } else { newItem.ident = ident; } } const keys = Object.keys(item).filter(function(key) { return !["options", "query"].includes(key); }); for (const key of keys) { newItem[key] = item[key]; } return newItem; } static normalizeCondition(condition) { if (!condition) throw new Error("Expected condition but got falsy value"); if (typeof condition === "string") { return str => str.indexOf(condition) === 0; } if (typeof condition === "function") { return condition; } if (condition instanceof RegExp) { return condition.test.bind(condition); } if (Array.isArray(condition)) { const items = condition.map(c => RuleSet.normalizeCondition(c)); return orMatcher(items); } if (typeof condition !== "object") { throw Error( "Unexcepted " + typeof condition + " when condition was expected (" + condition + ")" ); } const matchers = []; Object.keys(condition).forEach(key => { const value = condition[key]; switch (key) { case "or": case "include": case "test": if (value) matchers.push(RuleSet.normalizeCondition(value)); break; case "and": if (value) { const items = value.map(c => RuleSet.normalizeCondition(c)); matchers.push(andMatcher(items)); } break; case "not": case "exclude": if (value) { const matcher = RuleSet.normalizeCondition(value); matchers.push(notMatcher(matcher)); } break; default: throw new Error("Unexcepted property " + key + " in condition"); } }); if (matchers.length === 0) { throw new Error("Excepted condition but got " + condition); } if (matchers.length === 1) { return matchers[0]; } return andMatcher(matchers); } exec(data) { const result = []; this._run( data, { rules: this.rules }, result ); return result; } _run(data, rule, result) { // test conditions if (rule.resource && !data.resource) return false; if (rule.realResource && !data.realResource) return false; if (rule.resourceQuery && !data.resourceQuery) return false; if (rule.compiler && !data.compiler) return false; if (rule.issuer && !data.issuer) return false; if (rule.resource && !rule.resource(data.resource)) return false; if (rule.realResource && !rule.realResource(data.realResource)) return false; if (data.issuer && rule.issuer && !rule.issuer(data.issuer)) return false; if ( data.resourceQuery && rule.resourceQuery && !rule.resourceQuery(data.resourceQuery) ) { return false; } if (data.compiler && rule.compiler && !rule.compiler(data.compiler)) { return false; } // apply const keys = Object.keys(rule).filter(key => { return ![ "resource", "realResource", "resourceQuery", "compiler", "issuer", "rules", "oneOf", "use", "enforce" ].includes(key); }); for (const key of keys) { result.push({ type: key, value: rule[key] }); } if (rule.use) { const process = use => { if (typeof use === "function") { process(use(data)); } else if (Array.isArray(use)) { use.forEach(process); } else { result.push({ type: "use", value: use, enforce: rule.enforce }); } }; process(rule.use); } if (rule.rules) { for (let i = 0; i < rule.rules.length; i++) { this._run(data, rule.rules[i], result); } } if (rule.oneOf) { for (let i = 0; i < rule.oneOf.length; i++) { if (this._run(data, rule.oneOf[i], result)) break; } } return true; } findOptionsByIdent(ident) { const options = this.references[ident]; if (!options) { throw new Error("Can't find options with ident '" + ident + "'"); } return options; } };