Skip to content

Instantly share code, notes, and snippets.

@devonzuegel
Last active June 18, 2017 00:20
Show Gist options
  • Save devonzuegel/d1f12ad3d633546b495b3647745bdb80 to your computer and use it in GitHub Desktop.
Save devonzuegel/d1f12ad3d633546b495b3647745bdb80 to your computer and use it in GitHub Desktop.
Unambiguous Webpack config with Typescript
interface Foo {
bar: string|number
}
interface Baz extends Foo {
bar: string
}
const qux: Foo = {bar: 1} // Good
const corg: Baz = {bar: 1} // TypeError: Types of property 'bar' are incompatible
import * as webpack from 'webpack'
export interface MyConfig extends webpack.Configuration {
resolve: webpack.NewResolve
}
import * as webpack from 'webpack'
interface Config extends webpack.Configuration {
module: {
rules: webpack.NewUseRule[]
}
}
const config: Config = {
module: {
rules: [{
test: /\.css$/, // Match any files that end with ".css"
use: 'style-loader', // Pipe these files through style-loader upon import
}]
}
}
export default config
/** In the module request **/
const oneLoaderAbsolute = require("my-loader!./my-awesome-module")
const oneLoaderRelative = require("./loaders/my-loader!./my-awesome-module")
const chainedLoaders = require("style-loader!css-loader!less-loader!./my-styles.less")
const withQueryParams = require("loader?with=parameter!./file")
/** By config **/
{
module: {
rules: [
/**
* The following four loaders are equivalent. They each pipe file with
* the extension ".jsx" through the Babel loader with the es2015 preset.
**/
{
test: /\.jsx$/,
loader: "babel-loader",
options: {presets: ['es2015']},
}, {
test: /\.jsx$/,
loader: "babel-loader?presets[]=es2015",
}, {
test: /\.jsx$/,
loader: "babel?presets[]=es2015",
}, {
test: /\.jsx$/,
loaders: [
loader: "babel-loader",
options: {presets: ['es2015']},
],
},
/**
* The following three loaders are equivalent. They each pipe file with the
* extension ".less" through the less, css, and style loaders, in that order.
**/
{
test: /\.less$/,
loader: "style-loader!css-loader!less-loader",
}, {
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
}, {
test: /\.less$/,
loaders: ["style-loader", "css-loader", "less-loader"],
},
/** ... and more. This is nowhere near comprehensive! **/
]
},
}
import * as webpack from 'webpack'
const config: webpack.Configuration = { /* We'll add stuff here :) */ }
export default config
import * as webpack from 'webpack'
const config: webpack.Configuration = {
module: {
rules: [{
test: /\.css$/, // Match any files that end with ".css"
use: 'style-loader', // Pipe these files through style-loader upon import
}]
}
}
export default config
interface Configuration {
// ... There's other stuff in this interface
module?: Module;
}
{
/**
* Choose a style of source mapping to enhance the debugging process.
* These values can affect build and rebuild speed dramatically.
**/
devtool?: 'eval' | 'inline-source-map' | 'cheap-eval-source-map' | 'cheap-source-map' | 'cheap-module-eval-source-map' | 'cheap-module-source-map' | 'eval-source-map' | 'source-map' |
'nosources-source-map' | 'hidden-source-map' | 'nosources-source-map' | '@eval' | '@inline-source-map' | '@cheap-eval-source-map' | '@cheap-source-map' | '@cheap-module-eval-source-map' |
'@cheap-module-source-map' | '@eval-source-map' | '@source-map' | '@nosources-source-map' | '@hidden-source-map' | '@nosources-source-map' | '#eval' | '#inline-source-map' |
'#cheap-eval-source-map' | '#cheap-source-map' | '#cheap-module-eval-source-map' | '#cheap-module-source-map' | '#eval-source-map' | '#source-map' | '#nosources-source-map' |
'#hidden-source-map' | '#nosources-source-map' | '#@eval' | '#@inline-source-map' | '#@cheap-eval-source-map' | '#@cheap-source-map' | '#@cheap-module-eval-source-map' |
'#@cheap-module-source-map' | '#@eval-source-map' | '#@source-map' | '#@nosources-source-map' | '#@hidden-source-map' | '#@nosources-source-map' | boolean;
}
interface OldModule extends BaseModule {
loaders: Rule[]; /** An array of automatically applied loaders. */
}
interface NewModule extends BaseModule {
rules: Rule[]; /** An array of rules applied for modules. */
}
type Module = OldModule | NewModule;
{
rules: [ // These 3 rules are equivalent:
{
test: /\.jsx$/,
loader: "babel-loader",
}, {
test: /\.jsx$/,
use: ["babel-loader"],
}, {
test: /\.jsx$/,
use: "babel-loader",
},
],
// ...
}
/**
* There are direct and delegate rules. Direct Rules need a test,
* Delegate rules delegate to subrules bringing their own. Direct
* rules can optionally contain delegate keys (oneOf, rules).
*
* These types exist to enforce that a rule has the keys
* `((loader XOR loaders) AND test) OR oneOf OR rules`.
*/
interface BaseRule {
enforce?: 'pre' | 'post';
test?: Condition | Condition[]; /** A condition that must be met */
exclude?: Condition | Condition[]; /** A condition that must not be met */
include?: Condition | Condition[]; /** A condition that must be met */
resource?: Condition | Condition[]; /** A Condition matched with the resource. */
issuer?: Condition | Condition[]; /** A condition matched with the issuer */
/**
* An object with parser options. All applied parser options are merged.
* For each different parser options object a new parser is created
* and plugins can apply plugins depending on the parser options.
* Many of the default plugins apply their parser plugins only if
* a property in the parser options is not set or true.
*/
parser?: { [optName: string]: any };
rules?: Rule[]; /** An array of Rules that is also used when the Rule matches. */
oneOf?: Rule[]; /** An array of Rules from which only the first matching Rule is used when the Rule matches. */
}
interface BaseDirectRule extends BaseRule {
test: Condition | Condition[]; /** A condition that must be met */
}
// Direct Rules
interface BaseSingleLoaderRule extends BaseDirectRule {
loader: Loader; /** Loader name or an object with name and options */
}
interface OldLoaderRule extends BaseSingleLoaderRule {
query?: { [name: string]: any }; /** Loader options. @deprecated: */
}
interface NewLoaderRule extends BaseSingleLoaderRule {
options?: { [name: string]: any };
}
type LoaderRule = OldLoaderRule | NewLoaderRule;
interface OldUseRule extends BaseDirectRule {
loaders: string[]; /** A array of loaders. @deprecated use `use` instead */
}
interface NewUseRule extends BaseDirectRule {
use: Loader | Loader[]; /** A loader or array of loaders */
}
type UseRule = OldUseRule | NewUseRule;
// Delegate Rules
interface RulesRule extends BaseRule {
rules: Rule[]; /** An array of Rules that is also used when the Rule matches. */
}
interface OneOfRule extends BaseRule {
oneOf: Rule[];
}
type Rule = LoaderRule | UseRule | RulesRule | OneOfRule;
interface NewUseRule extends BaseRule {
test: Condition | Condition[]; /** A condition that must be met */
use: Loader | Loader[]; /** A loader or array of loaders */
}
type Rule = NewUseRule;
{
test: /\.css\?inline$/,
use: ['url-loader'],
}, {
test: /\.css\?external$/,
use: ['file-loader'],
},
interface BaseRule {
enforce?: 'pre' | 'post';
test?: Condition | Condition[]; /** A condition that must be met */
exclude?: Condition | Condition[]; /** A condition that must not be met */
include?: Condition | Condition[]; /** A condition that must be met */
resource?: Condition | Condition[]; /** A Condition matched with the resource. */
issuer?: Condition | Condition[]; /** A condition matched with the issuer */
parser?: { [optName: string]: any };
rules?: Rule[]; /** An array of Rules that is also used when the Rule matches. */
oneOf?: Rule[]; /** An array of Rules from which only the first matching Rule is used when the Rule matches. */
}
// Direct Rules
interface NewUseRule extends BaseDirectRule {
test: Condition | Condition[]; /** A condition that must be met */
use: Loader | Loader[]; /** A loader or array of loaders */
}
// Delegate Rules
interface RulesRule extends BaseRule {
rules: Rule[]; /** An array of Rules that is also used when the Rule matches. */
}
interface OneOfRule extends BaseRule {
oneOf: Rule[];
}
type Rule = NewUseRule | RulesRule | OneOfRule;
import inlineStyles from './styles?inline' // Will be loaded with url-loader
import externalStyles from './styles?external' // Will be loaded with file-loader
/** The following rules would make the above imports behave differently: **/
{
test: /\.css$/,
oneOf: [
{
resourceQuery: /inline/,
use: 'url-loader',
}, {
resourceQuery: /external/,
use: 'file-loader',
},
],
},
{
test: /\.css$/,
rules: [
{
issuer: /\.js$/,
use: 'style-loader',
}, {
use: 'css-loader',
},
],
},
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment