├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── app ├── car.js ├── car.spec.js ├── engine.js ├── engine.spec.js └── index.html ├── custom-loader.js ├── favicon.ico ├── index.html ├── index.js ├── package.json ├── readme.md ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015"] 4 | ], 5 | "sourceMaps": true, 6 | "retainLines": true 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 80 10 | 11 | [*.json] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/**/* -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "node": true 5 | }, 6 | "rules": { 7 | "indent": ["error", 2, { "SwitchCase": 1 }], 8 | "semi": ["error", "always"], 9 | "comma-dangle": ["error", "always-multiline"], 10 | "quotes": ["error", "single", { "allowTemplateLiterals": true }] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | dist/ 4 | *.log -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "8" 5 | - "10" 6 | script: 7 | - npm test 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | //from https://github.com/katopz/vscode-debug-nodejs-es6 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch", 7 | "type": "node", 8 | "request": "launch", 9 | "program": "${workspaceRoot}/index.js", 10 | "stopOnEntry": false, 11 | "args": [], 12 | "cwd": "${workspaceRoot}", 13 | "preLaunchTask": null, 14 | "runtimeExecutable": null, 15 | "runtimeArgs": [ 16 | "--nolazy" 17 | ], 18 | "env": { 19 | "NODE_ENV": "development" 20 | }, 21 | "console": "internalConsole", 22 | "sourceMaps": true, 23 | "outFiles": [] 24 | }, 25 | { 26 | "name": "Attach", 27 | "type": "node", 28 | "request": "attach", 29 | "port": 5858, 30 | "address": "localhost", 31 | "restart": false, 32 | "sourceMaps": false, 33 | "outFiles": [], 34 | "localRoot": "${workspaceRoot}", 35 | "remoteRoot": null 36 | }, 37 | { 38 | "name": "Attach to Process", 39 | "type": "node", 40 | "request": "attach", 41 | "processId": "${command.PickProcess}", 42 | "port": 5858, 43 | "sourceMaps": false, 44 | "outFiles": [] 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /app/car.js: -------------------------------------------------------------------------------- 1 | import { V8Engine } from './engine'; 2 | 3 | export class SportsCar { 4 | constructor(engine) { 5 | this.engine = engine; 6 | } 7 | 8 | toString() { 9 | return this.engine.toString() + ' Sports Car'; 10 | } 11 | } 12 | 13 | console.log( 14 | new SportsCar(new V8Engine()).toString() 15 | ); 16 | -------------------------------------------------------------------------------- /app/car.spec.js: -------------------------------------------------------------------------------- 1 | 2 | import { V6Engine, V8Engine, getVersion } from './engine'; 3 | import { SportsCar } from './car'; 4 | 5 | describe('Car - ',() => { 6 | it('should have a V8 Engine',() => { 7 | let car = new SportsCar(new V8Engine()); 8 | expect(car.toString()).toBe('V8 Sports Car'); 9 | }); 10 | 11 | it('should have a V6 Engine',() => { 12 | let car = new SportsCar(new V6Engine()); 13 | expect(car.toString()).toBe('V6 Sports Car'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/engine.js: -------------------------------------------------------------------------------- 1 | export class V6Engine { 2 | toString() { 3 | return 'V6'; 4 | } 5 | } 6 | 7 | export class V8Engine { 8 | toString() { 9 | return 'V8'; 10 | } 11 | } 12 | 13 | export function getVersion() { 14 | return '1.0'; 15 | } 16 | -------------------------------------------------------------------------------- /app/engine.spec.js: -------------------------------------------------------------------------------- 1 | import { V6Engine, V8Engine, getVersion } from './engine'; 2 | 3 | describe('Engine - ',() => { 4 | it('should have a v6 engine', () => { 5 | let v6 = new V6Engine(); 6 | expect(v6.toString()).toBe('V6'); 7 | }); 8 | 9 | it('should have a v8 engine', () => { 10 | let v8 = new V8Engine(); 11 | expect(v8.toString()).toBe('V8'); 12 | }); 13 | 14 | it('should get version', () => { 15 | expect(getVersion()).toBe('1.0'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Webpack Developer Kit 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /custom-loader.js: -------------------------------------------------------------------------------- 1 | module.exports = loader; 2 | 3 | // The simplest loaders are functions which take a files source (as raw string) 4 | // and return the modified source. This is a great starting place to learn to use loaders. 5 | function loader(source) { 6 | debugger; 7 | return source; 8 | } 9 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheLarkInn/webpack-developer-kit/b5aef19195070175584d0dcbecb544f5f67d467c/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Webpack Developer Kit 7 | 8 | 9 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('./app/car.js'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-developer-kit", 3 | "version": "1.0.0", 4 | "description": "Super Light Dev Kit for Webpack Developers", 5 | "main": "", 6 | "scripts": { 7 | "dev": "webpack-dev-server --inline --content-base build/", 8 | "build": "rimraf ./dist && webpack --config webpack.config.js", 9 | "lint": "eslint .", 10 | "test": "jest", 11 | "debug": "node --inspect --debug-brk ./node_modules/webpack/bin/webpack.js --config webpack.config.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/blacksonic/babel-webpack-tree-shaking.git" 16 | }, 17 | "keywords": [ 18 | "tree-shaking", 19 | "babel", 20 | "webpack", 21 | "toolkit", 22 | "developer" 23 | ], 24 | "author": { 25 | "name": "Sean Larkin", 26 | "email": "sean.larkin@cuw.edu" 27 | }, 28 | "license": "MIT", 29 | "jest": { 30 | "rootDir": "./app/" 31 | }, 32 | "dependencies": {}, 33 | "devDependencies": { 34 | "@types/acorn": "^4.0.3", 35 | "babel-core": "^6.22.1", 36 | "babel-eslint": "^7.1.1", 37 | "babel-loader": "^7.1.4", 38 | "babel-preset-es2015": "^6.22.0", 39 | "babel-preset-latest": "^6.22.0", 40 | "babel-register": "^6.22.0", 41 | "babili": "^0.0.10", 42 | "babili-webpack-plugin": "^0.1.2", 43 | "cheerio": "0.22.0", 44 | "chromedriver": "2.27.2", 45 | "enhanced-resolve": "^4.0.0", 46 | "eslint": "^3.14.1", 47 | "html-webpack-plugin": "^3.2.0", 48 | "http-server": "^0.9.0", 49 | "jasmine": "2.5.3", 50 | "jest": "18.1.0", 51 | "rimraf": "^2.5.4", 52 | "typescript": "^2.9.0-dev.20180428", 53 | "webpack": "^4.6.0", 54 | "webpack-cli": "^2.0.15", 55 | "webpack-dev-server": "^3.1.3", 56 | "webpack-sources": "^1.1.0" 57 | }, 58 | "babel": { 59 | "presets": [ 60 | "es2015" 61 | ], 62 | "sourceMaps": true, 63 | "retainLines": true 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # webpack Developer Kit 2 | 3 | Super lightweight boilerplate tailored to help developers understand how to create custom loaders and plugins. 4 | 5 | ## Requirements 6 | 7 | * Node 6.3 or higher (for native `node --inspect`) 8 | 9 | ## Usage 10 | 11 | Fork and clone this repo and then run `npm install` 12 | 13 | ## NPM Scripts 14 | 15 | There are two scripts that are setup already: 16 | 17 | * `npm run dev` 18 | 19 | * will run the same configuration instead with webpack-dev-server for live reload 20 | 21 | * `npm run build` 22 | 23 | * will simply execute a webpack build in the repo 24 | 25 | * `npm run debug` 26 | _ will run the same build with node debugger. 27 | _ paste provided link in Chrome (or Canary), and you will have the super incredible ChromeDevTools to step through your code for learning, exploration, and debugging. 28 | 29 | ## Whats in the config? 30 | 31 | You will notice **two things** that are setup already for you that should assist in learning to rapidly write custom plugins and loaders: 32 | 33 | * An `inline` webpack plugin already defined. You can use an anon. function inside of the plugins array which will then allow you to `plugin` to the `compiler` instance (which is `this`): 34 | 35 | ```javascript 36 | plugins: [ 37 | // This function is the `apply` function if you were to create an external plugin 38 | // Having it inline provides some nice conviences for debugging and development 39 | function apply() { 40 | /** @type {Compiler} */ 41 | var compiler = this; 42 | compiler.hooks.compilation.tap('MyCustomInlinePlugin', compilationTapFunction); 43 | }, 44 | /* ... */ 45 | ], 46 | ``` 47 | 48 | You'll now notice that I've also added JSDOC annotations through the code. This allows you to leverage powerful type inference capabilities via VS Code, and TypeScript Language Server, to show what types, properties, and methods the arguments, and parameters contain. This is incredibly great combined with `CMD/CTRL + click + hover`. This should make understanding and writing plugins a bit easier in this kit. 49 | 50 | * A `custom-loader` in the project root, and configuration for resolving it so you can use. Loaders by default automatically resolve from `node_modules` and therefore the `resolveLoader` property on the config allows you to have an `alias`'d resolve for that loader so you can work on it right in your project. (Much easier than creating a npm module, npm link and npm link module-name): 51 | 52 | ```javascript 53 | module.exports = loader; 54 | 55 | function loader(source) { 56 | console.log(source); 57 | debugger; 58 | return source; 59 | } 60 | ``` 61 | 62 | ## Helpful resources: 63 | 64 | * [Writing a loader](https://webpack.js.org/contribute/writing-a-loader/) 65 | * [Writing a plugin](https://webpack.js.org/contribute/writing-a-plugin/) 66 | * [webpack-sources](https://github.com/webpack/webpack-sources) 67 | * [enhanced-resolve](https://github.com/webpack/enhanced-resolve) 68 | 69 | ## Questions, issues, comments? 70 | 71 | > _Sean, I have a great suggestion for this repo, or notice a bug_ 72 | 73 | Submit an [issue](https://github.com/TheLarkInn/webpack-developer-kit/issues/new) or a [pull request](https://github.com/TheLarkInn/webpack-developer-kit/compare) for the feature. If I don't respond in a few days, tag me @TheLarkInn or [tweet me](https://twitter.com/TheLarkInn) at the same name (I'm terrible with email notifications). 74 | 75 | > _Sean, I want to know how to do this, or that with a loader or plugin_ 76 | 77 | Please submit an issue to [the webpack core repository](https://github.com/webpack/webpack/issues/new). Most times, if you are trying to write a custom loader or plugin, one of the contributors or @Sokra, or myself will be able to provide you with guidance. Plus, if it is an undocumented, or poorly documented feature, then we will tag it as documentation and move a copy of it over to our [new docs page](https://github.com/webpack/webpack.io) (Or even better if you find out you can submit a PR to our doc page.) 78 | 79 | ## Recognition 80 | 81 | I came up with this idea after forking [blacksonics](https://github.com/TheLarkInn/webpack-developer-kit/commits/master/readme.md?author=blacksonic) babel-treeshaking test repo. As I was debugging it, I found that "Hey, this would be pretty useful for people who want to write their own stuff". Thus you will see in initial git history, his mark on files like the `readme`. 82 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Plugins and extra supporting libs for webpack you might want to extend in your labratory! 😍 3 | const webpack = require('webpack'); 4 | // const memoryFs = require('memory-fs'); 5 | // const webpackSources = require('webpack-sources'); 6 | // const enhancedResolve = require('enhanced-resolve'); 7 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 8 | const path = require('path'); 9 | 10 | /** 11 | * @description 12 | * Typedef's so that you can access type 13 | * intellisense for each class and instance 14 | * inside of your hooks and parameters 15 | * 16 | * Use @typedef `import("webpack/lib/WhateverClass")` if you want to pull in a class and its properties from webpack!!! 17 | * 18 | */ 19 | const welcome = 'webpack-developer-kit'; 20 | console.log(welcome); 21 | /** @typedef {import("webpack/lib/Compiler")} Compiler */ 22 | /** @typedef {import("webpack/lib/Compilation")} Compilation */ 23 | /** @typedef {import("webpack/lib/NormalModule")} NormalModule */ 24 | /** @typedef {import("webpack/lib/ContextModule")} ContextModule */ 25 | /** @typedef {import("webpack/lib/NormalModuleFactory")} NormalModuleFactory */ 26 | /** @typedef {import("webpack/lib/ContextModuleFactory")} ContextModuleFactory */ 27 | /** @typedef {import("webpack/lib/Module")} Module */ 28 | /** @typedef {import("webpack/lib/Chunk")} Chunk */ 29 | /** @typedef {import("webpack/lib/Parser")} Parser */ 30 | /** @typedef {import("webpack/lib/ChunkGroup")} ChunkGroup */ 31 | /** @typedef {import("webpack/lib/Dependency")} Dependency */ 32 | /** @typedef {import("@types/acorn").Node} Node */ 33 | /** @typedef {{normalModuleFactory: NormalModuleFactory, contextModuleFactory: ContextModuleFactory, compilationDependencies: Set}} CompilationParams */ 34 | 35 | module.exports = { 36 | entry: { 37 | car: ['./app/car.js'], 38 | }, 39 | output: { 40 | path: path.join(__dirname, '/dist'), 41 | filename: '[name].bundle.js', 42 | }, 43 | module: { 44 | rules: [ 45 | // We are chaining the custom loader to babel loader. 46 | // Purely optional but know that the `first` loader in the chain (babel in this case) 47 | // must always return JavaScript (as it is then processed into the compilation) 48 | { 49 | test: /\.js$/, 50 | oneOf: [ 51 | { 52 | loaders: ['babel-loader', 'custom-loader'], 53 | }, 54 | ], 55 | }, 56 | ], 57 | }, 58 | // This allows us to add resolving functionality for our custom loader 59 | // It's used just like the resolve property and we are referencing the 60 | // custom loader file. 61 | resolveLoader: { 62 | alias: { 63 | 'custom-loader': require.resolve('./custom-loader'), 64 | }, 65 | }, 66 | plugins: [ 67 | // This function is the `apply` function if you were to create an external plugin 68 | // Having it inline provides some nice conviences for debugging and development 69 | function apply() { 70 | /** @type {Compiler} */ 71 | var compiler = this; 72 | compiler.hooks.compilation.tap('MyCustomInlinePlugin', compilationTapFunction); 73 | }, 74 | new HtmlWebpackPlugin({ 75 | template: './app/index.html', 76 | }), 77 | ], 78 | devtool: 'source-map', 79 | mode: 'none', 80 | }; 81 | 82 | /** 83 | * @param {Compilation} compilation 84 | * @param {CompilationParams} params 85 | */ 86 | function compilationTapFunction(compilation, { normalModuleFactory, contextModuleFactory, _compilationDependencies }) { 87 | compilation.hooks.afterOptimizeModules.tap('MyCustomInlinePlugin', modulesTapFunction); 88 | compilation.hooks.afterOptimizeChunks.tap('MyCustomInlinePlugin', chunksTapFunction); 89 | 90 | // Inline Tap Functions for ModuleFactories 91 | normalModuleFactory.hooks.beforeResolve.tapAsync('MyCustomInlinePlugin', (_data, cb) => { 92 | // debugger; 93 | cb(); 94 | }); 95 | contextModuleFactory.hooks.afterResolve.tapAsync('MyCustomInlinePlugin', (_data, cb) => { 96 | // debugger; 97 | cb(); 98 | }); 99 | 100 | normalModuleFactory.hooks.parser.for('javascript/esm').tap('MyCustomInlinePlugin', parserTapFunction); 101 | } 102 | 103 | /** 104 | * `afterOptimizeModules` tap function 105 | * @param {(Module|ContextModule|NormalModule)[]} _modules 106 | */ 107 | function modulesTapFunction(_modules) { 108 | // debugger; 109 | } 110 | 111 | /** 112 | * `afterOptimizeModules` tap function 113 | * @param {Chunk[]} _chunks 114 | * @param {ChunkGroup[]} _chunkGroups 115 | */ 116 | function chunksTapFunction(_chunks, _chunkGroups) { 117 | // debugger; 118 | // uncomment the statement above and run the debug script to explore the data in this function/hook/tap 119 | } 120 | 121 | /** 122 | * 123 | * @param {Parser} parser 124 | * @param {*} _parserOptions 125 | */ 126 | function parserTapFunction(parser, _parserOptions) { 127 | parser.hooks.expression.for('this').tap('MyCustomInlinePlugin', parserExpressionTopLevelThisTapFunction); 128 | // debugger; 129 | // uncomment the statement above and run the debug script to explore the data in this function/hook/tap 130 | } 131 | 132 | /** 133 | * When you tap into a parser hook, you are getting an event for when 134 | * the parser comes across a sepcific expression/syntax. This function is broken out so you can see type 135 | * support for the acorn.Node instance. 136 | * @param {Node} _node 137 | */ 138 | function parserExpressionTopLevelThisTapFunction(_node) { 139 | // debugger; 140 | // console.log(_node.type); 141 | // uncomment these statements above and run the debug script to explore the data in this function/hook/tap 142 | } 143 | --------------------------------------------------------------------------------