├── .gitignore ├── .npmignore ├── Makefile ├── README.md ├── package.json └── src ├── bin └── wpack.js └── config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | examples/webpack/bundle 4 | site/bundle 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | npm-debug.log 3 | .nyc_output/ 4 | coverage/ 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DELETE_ON_ERROR: 2 | 3 | BIN = ./node_modules/.bin 4 | TESTS = $(shell find src -path '*/__tests__/*-test.js') 5 | SRC = $(filter-out $(TESTS) $(FIXTURES), $(shell find src -name '*.js')) 6 | LIB = $(SRC:src/%=lib/%) 7 | MOCHA_OPTS = -R dot --require babel-core/register 8 | 9 | build:: 10 | @$(MAKE) -j 8 $(LIB) 11 | 12 | build-silent:: 13 | @$(MAKE) -s -j 8 $(LIB) 14 | 15 | lint:: 16 | @$(BIN)/eslint src 17 | 18 | check:: 19 | @$(BIN)/flow --show-all-errors src 20 | 21 | test:: test-unit test-doc 22 | 23 | test-unit:: 24 | @$(BIN)/mocha $(MOCHA_OPTS) $(TESTS) 25 | 26 | test-doc:: build-silent 27 | @$(BIN)/mocha $(MOCHA_OPTS) --compilers md:mocha-doctest ./README.md 28 | 29 | ci-doc:: 30 | @$(BIN)/mocha $(MOCHA_OPTS) --compilers md:mocha-doctest --watch ./README.md 31 | 32 | ci-unit:: 33 | @$(BIN)/mocha $(MOCHA_OPTS) --watch --watch-extensions json,md $(TESTS) 34 | 35 | sloc:: 36 | @$(BIN)/sloc -e __tests__ src 37 | 38 | version-major version-minor version-patch:: lint check test 39 | @npm version $(@:version-%=%) 40 | 41 | publish:: build 42 | @git push --tags origin HEAD:master 43 | @npm publish 44 | 45 | clean:: 46 | @rm -rf lib 47 | 48 | lib/%.js: src/%.js 49 | @echo "Building $<" 50 | @mkdir -p $(@D) 51 | @$(BIN)/babel $(BABEL_OPTIONS) -o $@ $< 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wpack 2 | 3 | wpack is a [webpack][] configuration API. 4 | 5 | ## Installation & Usage 6 | 7 | Install with [pnpm][]: 8 | 9 | ``` 10 | % pnpm install wpack 11 | ``` 12 | 13 | or with npm if you are up for a cup of coffee while it runs: 14 | 15 | ``` 16 | % npm install wpack 17 | ``` 18 | 19 | ## Configuration 20 | 21 | wpack is a thin configuration layer on top of webpack and most of the webpack 22 | configuration works. 23 | 24 | ### Using .wpackrc 25 | 26 | You can put wpack configuration in `.wpackrc` file in [JSON5][] format. 27 | 28 | ### Using package.json 29 | 30 | You can put wpack configuration in `package.json` file under `"wpack"` key. 31 | 32 | ### Primer 33 | 34 | ```js 35 | { 36 | context: __dirname, 37 | 38 | entry: '.', 39 | 40 | output: 'bundle.js', 41 | 42 | bail: true, 43 | 44 | devtool: '#cheap-module-source-map', 45 | 46 | target: 'web', 47 | 48 | module: { 49 | 50 | // Loaders which are applied on the current package's source code 51 | loaders: { 52 | '**/*.js': 'babel', 53 | }, 54 | 55 | // Loaders which is applied even on content within node_modules/ 56 | globalLoaders: { 57 | '**/*.css': ['style', 'css'], 58 | '**/*.png': 'url', 59 | '**/*.gif': 'url', 60 | '**/*.gif': 'url', 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | [pnpm]: https://github.com/rstacruz/pnpm 67 | [webpack]: https://github.com/webpack/webpack 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wpack", 3 | "version": "0.1.0", 4 | "description": "Simplistic Webpack configuration facade", 5 | "main": "lib/index.js", 6 | "bin": { 7 | "wpack": "lib/bin/wpack.js" 8 | }, 9 | "scripts": { 10 | "test": "make lint test" 11 | }, 12 | "babel": { 13 | "presets": [ 14 | "es2015", 15 | "stage-1" 16 | ] 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/andreypopp/wpack.git" 21 | }, 22 | "keywords": [ 23 | "webpack" 24 | ], 25 | "author": "Andrey Popp <8mayday@gmail.com>", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/andreypopp/wpack/issues" 29 | }, 30 | "homepage": "https://github.com/andreypopp/wpack#readme", 31 | "dependencies": { 32 | "babel-core": "^6.8.0", 33 | "babel-loader": "^6.2.4", 34 | "css-loader": "^0.23.1", 35 | "debug": "^2.2.0", 36 | "extract-text-webpack-plugin": "^1.0.1", 37 | "file-loader": "^0.8.5", 38 | "minimatch": "^3.0.0", 39 | "null-loader": "^0.1.1", 40 | "style-loader": "^0.13.1", 41 | "url-loader": "^0.5.7", 42 | "validated": "^0.5.0", 43 | "webpack": "^1.13.0", 44 | "webpack-dev-server": "^1.14.1" 45 | }, 46 | "devDependencies": { 47 | "babel-cli": "^6.8.0", 48 | "babel-preset-es2015": "^6.6.0", 49 | "babel-preset-stage-1": "^6.5.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/bin/wpack.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * @copyright 2016-present, wpack team 4 | */ 5 | 6 | import path from 'path'; 7 | import webpack from 'webpack'; 8 | import program from 'commander'; 9 | import {ValidationError} from 'validated/schema'; 10 | import pkg from '../../package.json'; 11 | import {findConfig, configureWebpack} from '../config'; 12 | 13 | let args = program 14 | .version(pkg.version) 15 | .command('wpack [directory]') 16 | .parse(process.argv); 17 | 18 | let [directory = process.cwd()] = args.args; 19 | 20 | directory = path.resolve(directory); 21 | 22 | let config = null 23 | try { 24 | config = findConfig(directory); 25 | } catch (error) { 26 | if (error instanceof ValidationError) { 27 | console.error(error.message); 28 | process.exit(1); 29 | } else { 30 | throw error; 31 | } 32 | } 33 | 34 | let webpackConfig = configureWebpack(config); 35 | let compiler = webpack(webpackConfig); 36 | 37 | compiler.run(error => { 38 | if (error) { 39 | if (error.details) { 40 | console.error(error.details); 41 | } else { 42 | console.error(error.stack || error); 43 | } 44 | process.exit(1); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright 2016-present, wpack team 3 | */ 4 | 5 | import * as fs from 'fs'; 6 | import * as path from 'path'; 7 | import debug from 'debug'; 8 | import {Minimatch} from 'minimatch'; 9 | 10 | import { 11 | mapping, object, partialObject, sequence, oneOf, maybe, 12 | string, boolean, any, enumeration, 13 | ValidationError, validationError 14 | } from 'validated/schema'; 15 | import {validate as validateJSON5} from 'validated/json5'; 16 | 17 | let log = debug('wpack:config'); 18 | 19 | let outputSchema = oneOf( 20 | string.andThen(path => ({path})), 21 | object({ 22 | path: string, 23 | filename: maybe(string), 24 | }) 25 | ); 26 | 27 | let loaderSchemaSingle = oneOf( 28 | string.andThen(loader => ({loader})), 29 | object({loader: string, query: maybe(mapping())}) 30 | ).andThen(value => { 31 | if (loaders[value.loader]) { 32 | return {...value, loader: loaders[value.loader]}; 33 | } else { 34 | return value; 35 | } 36 | }); 37 | 38 | let loaderSchema = oneOf( 39 | loaderSchemaSingle, 40 | sequence(loaderSchemaSingle) 41 | ); 42 | 43 | let pluginSchema = any; 44 | 45 | let configSchemaSingle = object({ 46 | 47 | entry: string, 48 | 49 | output: maybe(outputSchema), 50 | 51 | debug: maybe(string), 52 | 53 | target: maybe(enumeration('web', 'node')), 54 | 55 | bail: maybe(boolean), 56 | 57 | devtool: maybe(enumeration( 58 | 'eval', 59 | 'source-map', 60 | 'inline-source-map', 61 | 'eval-source-map', 62 | 'cheap-source-map', 63 | 'hidden-source-map', 64 | 'cheap-module-source-map' 65 | )), 66 | 67 | module: maybe(object({ 68 | 69 | loaders: maybe(mapping(loaderSchema)), 70 | 71 | globalLoaders: maybe(mapping(loaderSchema)), 72 | 73 | })), 74 | 75 | plugins: maybe(sequence(pluginSchema)), 76 | 77 | }); 78 | 79 | let configSchema = object({ 80 | ...configSchemaSingle.values, 81 | profile: maybe(mapping(configSchemaSingle)), 82 | }); 83 | 84 | let configSchemaWithinPackageJSON = partialObject({ 85 | wpack: maybe(configSchema) 86 | }); 87 | 88 | export let loaders = { 89 | url: { 90 | loader: require.resolve('url-loader') 91 | }, 92 | file: { 93 | loader: require.resolve('file-loader'), 94 | }, 95 | babel: { 96 | loader: require.resolve('babel-loader'), 97 | }, 98 | css: { 99 | loader: require.resolve('css-loader'), 100 | }, 101 | style: { 102 | loader: require.resolve('style-loader'), 103 | }, 104 | image: { 105 | loader: require.resolve('url-loader'), 106 | query: {prefix: 'img/', limit: 5000} 107 | }, 108 | font: { 109 | loader: require.resolve('url-loader'), 110 | query: {prefix: 'font/', limit: 5000} 111 | }, 112 | legacyFont: { 113 | loader: require.resolve('file-loader'), 114 | query: {prefix: 'font/'} 115 | } 116 | }; 117 | 118 | export let defaultConfig = { 119 | debug: true, 120 | bail: true, 121 | devtool: 'cheap-module-eval-source-map', 122 | output: { 123 | filename: 'bundle.js', 124 | path: './build', 125 | }, 126 | module: { 127 | loaders: { 128 | '**/*.js': loaders.babel, 129 | }, 130 | globalLoaders: { 131 | '**/*.css': [loaders.style, loaders.css], 132 | '**/*.module.css': [loaders.style, {loader: loaders.css, query: {modules: true}}], 133 | 134 | '**/*.png': loaders.image, 135 | '**/*.jpg': loaders.image, 136 | '**/*.gif': loaders.image, 137 | 138 | '**/*.eot': loaders.legacyFont, 139 | '**/*.ttf': loaders.legacyFont, 140 | '**/*.svg': loaders.legacyFont, 141 | '**/*.woff': loaders.font, 142 | '**/*.woff2': loaders.font, 143 | }, 144 | }, 145 | }; 146 | 147 | export function mergeConfig(configA, configB) { 148 | let moduleA = configA.module || {}; 149 | let moduleB = configB.module || {}; 150 | return { 151 | ...configA, 152 | ...configB, 153 | output: { 154 | ...configA.output, 155 | ...configB.output, 156 | }, 157 | module: { 158 | loaders: { 159 | ...moduleA.loaders, 160 | ...moduleB.loaders 161 | }, 162 | globalLoaders: { 163 | ...moduleA.globalLoaders, 164 | ...moduleB.globalLoaders 165 | }, 166 | } 167 | }; 168 | } 169 | 170 | export function readConfig(filename) { 171 | let content = fs.readFileSync(filename, 'utf8'); 172 | return validateJSON5(configSchema, content); 173 | } 174 | 175 | export function readConfigFromPackage(directory) { 176 | let filename = path.join(directory, PACKAGEJSON); 177 | let content = fs.readFileSync(filename, 'utf8'); 178 | return validateJSON5(configSchemaWithinPackageJSON, content); 179 | } 180 | 181 | const WPACKRC = '.wpackrc'; 182 | const PACKAGEJSON = 'package.json'; 183 | 184 | export function findConfig(directory) { 185 | let config = findConfigImpl(directory) || {}; 186 | config = mergeConfig(defaultConfig, config); 187 | if (config.context === undefined) { 188 | config.context = directory; 189 | } 190 | return config; 191 | } 192 | 193 | function findConfigImpl(directory) { 194 | directory = path.join(directory, '__dummy_filename__'); 195 | while(path.dirname(directory) !== directory) { 196 | directory = path.dirname(directory); 197 | let wpackrc = path.join(directory, WPACKRC); 198 | let packagejson = path.join(directory, PACKAGEJSON); 199 | if (fs.existsSync(wpackrc)) { 200 | try { 201 | return readConfig(wpackrc); 202 | } catch (error) { 203 | if (error instanceof ValidationError) { 204 | throw enrichValidationErrorContext(error, `While validating ${wpackrc}`); 205 | } else { 206 | throw error; 207 | } 208 | } 209 | } else if (fs.existsSync(packagejson)) { 210 | let config; 211 | try { 212 | config = readConfigFromPackage(directory).wpack; 213 | } catch (error) { 214 | if (error instanceof ValidationError) { 215 | throw enrichValidationErrorContext(error, `While validating ${wpackrc}`); 216 | } else { 217 | throw error; 218 | } 219 | } 220 | if (config) { 221 | return config; 222 | } 223 | } 224 | } 225 | return null; 226 | } 227 | 228 | function enrichValidationErrorContext(error, ...messages) { 229 | return validationError( 230 | error.originalMessage, 231 | error.contextMessages.concat(messages) 232 | ); 233 | } 234 | 235 | function configureWebpackLoaders(context, loaders, options = {}) { 236 | let result = []; 237 | for (let pattern in loaders) { 238 | if (loaders.hasOwnProperty(pattern)) { 239 | let loader = loaders[pattern]; 240 | if (!options.global) { 241 | pattern = path.join(context, pattern); 242 | } 243 | let test = new Minimatch(pattern).makeRe(); 244 | if (Array.isArray(loader)) { 245 | result.push({ 246 | loaders: loader, 247 | test 248 | }); 249 | } else { 250 | result.push({ 251 | ...loader, 252 | test 253 | }); 254 | } 255 | } 256 | } 257 | return result; 258 | } 259 | 260 | export function configureWebpack(config) { 261 | return { 262 | ...config, 263 | output: { 264 | ...config.output, 265 | path: path.resolve(config.context, config.output.path) 266 | }, 267 | module: { 268 | loaders: [] 269 | .concat( 270 | configureWebpackLoaders(config.context, config.module.loaders)) 271 | .concat( 272 | configureWebpackLoaders(config.context, config.module.globalLoaders, {global: true})) 273 | } 274 | }; 275 | } 276 | --------------------------------------------------------------------------------