├── .gitignore ├── images └── screenshot.jpg ├── LICENSE ├── webpack.settings.js ├── package.json ├── webpack.dev.js ├── webpack.common.js ├── webpack.prod.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules -------------------------------------------------------------------------------- /images/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batchnz/craft-webpack/HEAD/images/screenshot.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Batch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack.settings.js: -------------------------------------------------------------------------------- 1 | // webpack.settings.js - webpack settings config 2 | 3 | // node modules 4 | require("dotenv").config(); 5 | 6 | // Webpack settings exports 7 | // noinspection WebpackConfigHighlighting 8 | module.exports = { 9 | name: "Batch Craft Starter", 10 | copyright: "Batch Development", 11 | paths: { 12 | working: process.cwd(), 13 | src: { 14 | base: "./src/", 15 | css: "./src/css/", 16 | js: "./src/js/", 17 | }, 18 | dist: { 19 | base: `${process.env.DOCROOT || "public"}/dist/`, 20 | clean: ["**/*"], 21 | }, 22 | templates: "./templates/", 23 | }, 24 | urls: { 25 | publicPath: () => process.env.PUBLIC_PATH || "/dist/", 26 | }, 27 | vars: { 28 | cssName: "styles", 29 | }, 30 | entries: { 31 | app: "app.js", 32 | }, 33 | babelLoaderConfig: { 34 | exclude: [/(node_modules|bower_components)/], 35 | }, 36 | devServerConfig: { 37 | public: () => process.env.DEVSERVER_PUBLIC || "http://localhost:8080", 38 | host: () => process.env.DEVSERVER_HOST || "localhost", 39 | poll: () => process.env.DEVSERVER_POLL || false, 40 | port: () => process.env.DEVSERVER_PORT || 8080, 41 | https: () => process.env.DEVSERVER_HTTPS || false, 42 | }, 43 | manifestConfig: { 44 | basePath: "", 45 | }, 46 | eslintConfig: { 47 | files: "src", 48 | extensions: [".js", ".vue"], 49 | baseConfig: { 50 | root: true, 51 | parser: "vue-eslint-parser", 52 | parserOptions: { 53 | parser: "babel-eslint", 54 | sourceType: "module", 55 | ecmaVersion: 2020, 56 | }, 57 | extends: [ 58 | "airbnb-base", 59 | "plugin:vue/recommended", 60 | "plugin:prettier/recommended", 61 | "prettier/vue", 62 | ], 63 | env: { 64 | node: true, 65 | browser: true, 66 | }, 67 | }, 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@batch/craft-webpack", 3 | "version": "1.5.0", 4 | "description": "Batch's Webpack setup made for the Craft Starter.", 5 | "keywords": [ 6 | "Batch", 7 | "Craft", 8 | "Webpack", 9 | "Starter" 10 | ], 11 | "homepage": "https://github.com/batchnz/craft-webpack#readme", 12 | "bugs": { 13 | "url": "https://github.com/batchnz/craft-webpack/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/batchnz/craft-webpack.git" 18 | }, 19 | "license": "MIT", 20 | "author": "Josh Smith ", 21 | "scripts": { 22 | "build": "webpack --config webpack.prod.js --progress --hide-modules", 23 | "dev": "webpack-dev-server --config webpack.dev.js" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | "> 1%", 28 | "last 2 versions", 29 | "Firefox ESR" 30 | ], 31 | "modernBrowsers": [ 32 | "> 0.5%", 33 | "last 2 versions and not dead", 34 | "Firefox ESR", 35 | "not Safari < 10.1", 36 | "not IE <= 11", 37 | "not IE_Mob <= 11", 38 | "not op_mini all" 39 | ], 40 | "legacyBrowsers": [ 41 | "> 1%", 42 | "last 2 versions", 43 | "Firefox ESR" 44 | ] 45 | }, 46 | "dependencies": { 47 | "@babel/core": "^7.11.6", 48 | "@babel/plugin-transform-runtime": "^7.11.5", 49 | "@babel/preset-env": "^7.11.5", 50 | "@gfx/zopfli": "^1.0.15", 51 | "babel-eslint": "^10.1.0", 52 | "babel-loader": "^8.1.0", 53 | "compression-webpack-plugin": "^5.0.2", 54 | "core-js": "3.6.5", 55 | "cross-spawn": "^7.0.3", 56 | "css-loader": "^3.6.0", 57 | "dotenv": "^8.2.0", 58 | "eslint": "^7.12.0", 59 | "eslint-config-airbnb-base": "^14.2.0", 60 | "eslint-config-prettier": "^6.14.0", 61 | "eslint-plugin-import": "^2.22.1", 62 | "eslint-plugin-prettier": "^3.1.4", 63 | "eslint-plugin-vue": "^7.1.0", 64 | "eslint-webpack-plugin": "^2.1.0", 65 | "file-loader": "^6.1.0", 66 | "glob-all": "^3.2.1", 67 | "ignore-loader": "^0.1.2", 68 | "imagemin-gifsicle": "^7.0.0", 69 | "imagemin-mozjpeg": "^9.0.0", 70 | "imagemin-optipng": "^8.0.0", 71 | "imagemin-svgo": "^8.0.0", 72 | "imagemin-webp-webpack-plugin": "^3.3.2", 73 | "img-loader": "^3.0.1", 74 | "mini-css-extract-plugin": "^0.11.2", 75 | "optimize-css-assets-webpack-plugin": "^5.0.4", 76 | "postcss": "^7.0.32", 77 | "postcss-advanced-variables": "^3.0.1", 78 | "postcss-atroot": "^0.1.3", 79 | "postcss-extend-rule": "^3.0.0", 80 | "postcss-import": "^12.0.1", 81 | "postcss-inline-svg": "^4.1.0", 82 | "postcss-loader": "^4.0.1", 83 | "postcss-nested": "^4.2.3", 84 | "postcss-preset-env": "^6.7.0", 85 | "postcss-property-lookup": "^2.0.0", 86 | "postcss-reporter": "^6.0.1", 87 | "prettier": "^2.1.2", 88 | "resolve-url-loader": "^3.1.2", 89 | "rimraf": "^3.0.2", 90 | "style-loader": "^1.2.1", 91 | "stylelint": "^13.7.1", 92 | "stylelint-config-recommended": "^3.0.0", 93 | "terser-webpack-plugin": "^4.2.0", 94 | "vue-loader": "^15.9.3", 95 | "vue-style-loader": "^4.1.2", 96 | "vue-svg-loader": "^0.16.0", 97 | "vue-template-compiler": "^2.6.12", 98 | "webpack": "^4.44.1", 99 | "webpack-cli": "^3.3.12", 100 | "webpack-dev-server": "^3.11.0", 101 | "webpack-manifest-plugin": "^2.2.0", 102 | "webpack-merge": "^5.1.4", 103 | "webpack-notifier": "^1.8.0" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | // webpack.dev.js - developmental builds 2 | const LEGACY_CONFIG = "legacy"; 3 | const MODERN_CONFIG = "modern"; 4 | 5 | // node modules 6 | const { merge } = require("webpack-merge"); 7 | const path = require("path"); 8 | const fs = require("fs"); 9 | const webpack = require("webpack"); 10 | // config files 11 | const common = require("./webpack.common.js"); 12 | const settings = require("./webpack.settings.js"); 13 | 14 | let projectConfig = {}; 15 | if (fs.existsSync(path.resolve(process.cwd(), "webpack.config.js"))) { 16 | projectConfig = require(path.resolve(process.cwd(), "webpack.config.js")); 17 | } 18 | 19 | // Configure the webpack-dev-server 20 | const configureDevServer = (buildType) => { 21 | return { 22 | public: settings.devServerConfig.public(), 23 | contentBase: path.resolve(process.cwd(), settings.paths.templates), 24 | host: settings.devServerConfig.host(), 25 | port: settings.devServerConfig.port(), 26 | https: !!parseInt(settings.devServerConfig.https()), 27 | disableHostCheck: true, 28 | hot: true, 29 | overlay: true, 30 | watchContentBase: true, 31 | watchOptions: { 32 | poll: !!parseInt(settings.devServerConfig.poll()), 33 | ignored: /node_modules/, 34 | }, 35 | headers: { 36 | "Access-Control-Allow-Origin": "*", 37 | }, 38 | }; 39 | }; 40 | 41 | // Configure Image loader 42 | const configureImageLoader = (buildType) => { 43 | return { 44 | test: /\.(png|jpe?g|gif|webp)$/i, 45 | use: [ 46 | { 47 | loader: require.resolve("file-loader"), 48 | options: { 49 | name: "img/[name].[hash].[ext]", 50 | }, 51 | }, 52 | ], 53 | }; 54 | }; 55 | 56 | // Configure SVG loader 57 | const configureSVGLoader = () => { 58 | return { 59 | test: /\.svg$/, 60 | rules: [ 61 | { 62 | oneOf: [ 63 | { 64 | loader: require.resolve("vue-svg-loader"), 65 | }, 66 | { 67 | resourceQuery: /^\?external/, 68 | loader: require.resolve("file-loader"), 69 | options: { 70 | name: "img/[name].[hash].[ext]", 71 | }, 72 | }, 73 | ], 74 | }, 75 | ], 76 | }; 77 | }; 78 | 79 | // Configure the Postcss loader 80 | const configurePostcssLoader = (buildType) => { 81 | return { 82 | test: /\.(pcss|css)$/, 83 | use: [ 84 | { 85 | loader: require.resolve("style-loader"), 86 | }, 87 | { 88 | loader: require.resolve("vue-style-loader"), 89 | }, 90 | { 91 | loader: require.resolve("css-loader"), 92 | options: { 93 | importLoaders: 2, 94 | sourceMap: true, 95 | }, 96 | }, 97 | { 98 | loader: require.resolve("resolve-url-loader"), 99 | }, 100 | { 101 | loader: require.resolve("postcss-loader"), 102 | options: { 103 | sourceMap: true, 104 | }, 105 | }, 106 | ], 107 | }; 108 | }; 109 | 110 | // Define the legacy webpack config 111 | const legacyConfig = merge( 112 | common.legacyConfig, 113 | { 114 | output: { 115 | filename: path.join("./js", "[name]-legacy.[hash].js"), 116 | publicPath: settings.devServerConfig.public() + "/", 117 | }, 118 | mode: "development", 119 | devtool: "eval-source-map", 120 | devServer: configureDevServer(LEGACY_CONFIG), 121 | module: { 122 | rules: [ 123 | configurePostcssLoader(LEGACY_CONFIG), 124 | configureImageLoader(LEGACY_CONFIG), 125 | configureSVGLoader(LEGACY_CONFIG), 126 | ], 127 | }, 128 | plugins: [new webpack.HotModuleReplacementPlugin()], 129 | }, 130 | projectConfig 131 | ); 132 | 133 | // Define the modern webpack config 134 | const modernConfig = merge( 135 | common.modernConfig, 136 | { 137 | output: { 138 | filename: path.join("./js", "[name].[hash].js"), 139 | publicPath: settings.devServerConfig.public() + "/", 140 | }, 141 | mode: "development", 142 | devtool: "eval-source-map", 143 | devServer: configureDevServer(MODERN_CONFIG), 144 | module: { 145 | rules: [ 146 | configurePostcssLoader(MODERN_CONFIG), 147 | configureImageLoader(MODERN_CONFIG), 148 | configureSVGLoader(MODERN_CONFIG), 149 | ], 150 | }, 151 | plugins: [new webpack.HotModuleReplacementPlugin()], 152 | }, 153 | projectConfig 154 | ); 155 | 156 | module.exports = (env) => { 157 | const BUILD_TYPE = env && env.BUILD_TYPE; 158 | 159 | // Output either a legacy, modern or combined config. 160 | // Defaults to modern for development. 161 | switch (BUILD_TYPE) { 162 | case "combined": 163 | return [legacyConfig, modernConfig]; 164 | case "legacy": 165 | return [legacyConfig]; 166 | case "modern": 167 | default: 168 | return [modernConfig]; 169 | } 170 | }; 171 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | // webpack.common.js - common webpack config 2 | const LEGACY_CONFIG = "legacy"; 3 | const MODERN_CONFIG = "modern"; 4 | 5 | // node modules 6 | const path = require("path"); 7 | const fs = require("fs"); 8 | const { merge, mergeWithCustomize, customizeObject } = require("webpack-merge"); 9 | 10 | // webpack plugins 11 | const ESLintPlugin = require("eslint-webpack-plugin"); 12 | const ManifestPlugin = require("webpack-manifest-plugin"); 13 | const VueLoaderPlugin = require("vue-loader/lib/plugin"); 14 | const WebpackNotifierPlugin = require("webpack-notifier"); 15 | 16 | // config files 17 | const pkg = require("./package.json"); 18 | let settings = require("./webpack.settings.js"); 19 | if (fs.existsSync(path.resolve(process.cwd(), "./webpack.settings.js"))) { 20 | let projectSettings = require(path.resolve( 21 | process.cwd(), 22 | "./webpack.settings.js" 23 | )); 24 | settings = merge(settings, projectSettings); 25 | } 26 | 27 | // Configure Babel loader 28 | const configureBabelLoader = (browserList) => { 29 | return { 30 | test: /\.js$/, 31 | exclude: settings.babelLoaderConfig.exclude, 32 | use: { 33 | loader: require.resolve("babel-loader"), 34 | options: { 35 | cacheDirectory: true, 36 | presets: [ 37 | [ 38 | "@babel/preset-env", 39 | { 40 | modules: false, 41 | corejs: { 42 | version: "3.6", 43 | proposals: true, 44 | }, 45 | useBuiltIns: "usage", 46 | targets: { 47 | browsers: browserList, 48 | }, 49 | }, 50 | ], 51 | ], 52 | plugins: [ 53 | "@babel/plugin-syntax-dynamic-import", 54 | "@babel/plugin-transform-runtime", 55 | ], 56 | }, 57 | }, 58 | }; 59 | }; 60 | 61 | // Configure Entries 62 | const configureEntries = () => { 63 | let entries = {}; 64 | for (const [key, value] of Object.entries(settings.entries)) { 65 | entries[key] = path.resolve( 66 | settings.paths.working, 67 | settings.paths.src.js + value 68 | ); 69 | } 70 | 71 | return entries; 72 | }; 73 | 74 | // Configure Font loader 75 | const configureFontLoader = () => { 76 | return { 77 | test: /\.(ttf|eot|woff2?)$/i, 78 | use: [ 79 | { 80 | loader: require.resolve("file-loader"), 81 | options: { 82 | name: "fonts/[name].[ext]", 83 | }, 84 | }, 85 | ], 86 | }; 87 | }; 88 | 89 | // Configure Manifest 90 | const configureManifest = (fileName) => { 91 | return { 92 | fileName: fileName, 93 | basePath: settings.manifestConfig.basePath, 94 | map: (file) => { 95 | file.name = file.name.replace(/(\.[a-f0-9]{32})(\..*)$/, "$2"); 96 | return file; 97 | }, 98 | }; 99 | }; 100 | 101 | // Configure Vue loader 102 | const configureVueLoader = () => { 103 | return { 104 | test: /\.vue$/, 105 | loader: require.resolve("vue-loader"), 106 | }; 107 | }; 108 | // The base webpack config 109 | const baseConfig = { 110 | name: pkg.name, 111 | entry: configureEntries(), 112 | output: { 113 | path: path.resolve(settings.paths.working, settings.paths.dist.base), 114 | publicPath: settings.urls.publicPath(), 115 | }, 116 | resolve: { 117 | alias: { 118 | vue$: "vue/dist/vue.esm.js", 119 | }, 120 | }, 121 | module: { 122 | rules: [configureFontLoader(), configureVueLoader()], 123 | }, 124 | plugins: [ 125 | new WebpackNotifierPlugin({ 126 | title: "Webpack", 127 | excludeWarnings: true, 128 | alwaysNotify: true, 129 | }), 130 | new VueLoaderPlugin(), 131 | ], 132 | }; 133 | 134 | // Legacy webpack config 135 | const legacyConfig = { 136 | module: { 137 | rules: [ 138 | configureBabelLoader(Object.values(pkg.browserslist.legacyBrowsers)), 139 | ], 140 | }, 141 | plugins: [new ManifestPlugin(configureManifest("manifest-legacy.json"))], 142 | }; 143 | 144 | // Modern webpack config 145 | const modernConfig = { 146 | module: { 147 | rules: [ 148 | configureBabelLoader(Object.values(pkg.browserslist.modernBrowsers)), 149 | ], 150 | }, 151 | plugins: [ 152 | new ESLintPlugin(settings.eslintConfig), 153 | new ManifestPlugin(configureManifest("manifest.json")), 154 | ], 155 | }; 156 | 157 | let legacyMerged = mergeWithCustomize({ 158 | legacyConfig: customizeObject({ 159 | module: "prepend", 160 | plugins: "prepend", 161 | }), 162 | })(baseConfig, legacyConfig); 163 | 164 | let modernMerged = mergeWithCustomize({ 165 | modernConfig: customizeObject({ 166 | module: "prepend", 167 | plugins: "prepend", 168 | }), 169 | })(baseConfig, modernConfig); 170 | 171 | // Common module exports 172 | // noinspection WebpackConfigHighlighting 173 | module.exports = { 174 | legacyConfig: legacyMerged, 175 | modernConfig: modernMerged, 176 | }; 177 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | // webpack.prod.js - production builds 2 | const LEGACY_CONFIG = "legacy"; 3 | const MODERN_CONFIG = "modern"; 4 | 5 | // node modules 6 | const glob = require("glob-all"); 7 | const path = require("path"); 8 | const fs = require('fs'); 9 | const { merge } = require('webpack-merge'); 10 | const webpack = require("webpack"); 11 | 12 | // webpack plugins 13 | const rimraf = require("rimraf"); 14 | const CompressionPlugin = require("compression-webpack-plugin"); 15 | const ImageminWebpWebpackPlugin = require("imagemin-webp-webpack-plugin"); 16 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 17 | const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); 18 | const TerserPlugin = require("terser-webpack-plugin"); 19 | const zopfli = require("@gfx/zopfli"); 20 | 21 | // config files 22 | const common = require("./webpack.common.js"); 23 | 24 | let settings = require("./webpack.settings.js"); 25 | if (fs.existsSync(path.resolve(process.cwd(), './webpack.settings.js'))) { 26 | let projectSettings = require(path.resolve(process.cwd(), './webpack.settings.js')); 27 | settings = merge(settings, projectSettings); 28 | } 29 | 30 | let projectConfig = {}; 31 | if (fs.existsSync(path.resolve(process.cwd(), 'webpack.config.js'))) { 32 | projectConfig = require(path.resolve(process.cwd(), 'webpack.config.js')); 33 | } 34 | 35 | // Clean build assets before continuing 36 | rimraf(path.resolve(settings.paths.working, settings.paths.dist.base), {}, () => 37 | console.log("\n\nRemoved all previous build assets.\n") 38 | ); 39 | 40 | // Configure Compression webpack plugin 41 | const configureCompression = () => { 42 | return { 43 | filename: "[path].gz[query]", 44 | test: /\.(js|css|html|svg)$/, 45 | threshold: 10240, 46 | minRatio: 0.8, 47 | deleteOriginalAssets: false, 48 | compressionOptions: { 49 | numiterations: 15, 50 | level: 9, 51 | }, 52 | algorithm(input, compressionOptions, callback) { 53 | return zopfli.gzip(input, compressionOptions, callback); 54 | }, 55 | }; 56 | }; 57 | 58 | // Configure Image loader 59 | const configureImageLoader = (buildType) => { 60 | if (buildType === LEGACY_CONFIG) { 61 | return { 62 | test: /\.(png|jpe?g|gif|webp)$/i, 63 | use: [ 64 | { 65 | loader: require.resolve("file-loader"), 66 | options: { 67 | name: "img/[name].[hash].[ext]", 68 | }, 69 | }, 70 | ], 71 | }; 72 | } 73 | if (buildType === MODERN_CONFIG) { 74 | return { 75 | test: /\.(png|jpe?g|gif|webp)$/i, 76 | use: [ 77 | { 78 | loader: require.resolve("file-loader"), 79 | options: { 80 | name: "img/[name].[hash].[ext]", 81 | }, 82 | }, 83 | { 84 | loader: require.resolve("img-loader"), 85 | options: { 86 | plugins: [ 87 | require("imagemin-gifsicle")({ 88 | interlaced: true, 89 | }), 90 | require("imagemin-mozjpeg")({ 91 | progressive: true, 92 | arithmetic: false, 93 | }), 94 | require("imagemin-optipng")({ 95 | optimizationLevel: 5, 96 | }), 97 | require("imagemin-svgo")({ 98 | plugins: [{ convertPathData: false }], 99 | }), 100 | ], 101 | }, 102 | }, 103 | ], 104 | }; 105 | } 106 | }; 107 | 108 | // Configure SVG loader 109 | const configureSvgLoader = (buildType) => { 110 | if (buildType === LEGACY_CONFIG) { 111 | return { 112 | test: /\.svg$/, 113 | oneOf: [ 114 | { 115 | loader: require.resolve("vue-svg-loader"), 116 | }, 117 | { 118 | resourceQuery: /external/, 119 | loader: require.resolve("file-loader"), 120 | query: { 121 | name: "img/[name].[hash].[ext]", 122 | }, 123 | }, 124 | ], 125 | }; 126 | } 127 | if (buildType === MODERN_CONFIG) { 128 | return { 129 | test: /\.svg$/, 130 | oneOf: [ 131 | { 132 | loader: require.resolve("vue-svg-loader"), 133 | }, 134 | { 135 | resourceQuery: /external/, 136 | use: [ 137 | { 138 | loader: require.resolve("file-loader"), 139 | query: { 140 | name: "img/[name].[hash].[ext]", 141 | }, 142 | }, 143 | { 144 | loader: require.resolve("img-loader"), 145 | options: { 146 | plugins: [ 147 | require("imagemin-svgo")({ 148 | plugins: [{ convertPathData: false }], 149 | }), 150 | ], 151 | }, 152 | }, 153 | ], 154 | }, 155 | ], 156 | }; 157 | } 158 | }; 159 | 160 | // Configure optimization 161 | const configureOptimization = (buildType) => { 162 | if (buildType === LEGACY_CONFIG) { 163 | return { 164 | splitChunks: { 165 | cacheGroups: { 166 | default: false, 167 | common: false, 168 | styles: { 169 | name: settings.vars.cssName, 170 | test: /\.(pcss|css|vue)$/, 171 | chunks: "all", 172 | enforce: true, 173 | }, 174 | }, 175 | }, 176 | minimizer: [ 177 | new TerserPlugin(configureTerser()), 178 | new OptimizeCSSAssetsPlugin({ 179 | cssProcessorOptions: { 180 | map: { 181 | inline: false, 182 | annotation: true, 183 | }, 184 | safe: true, 185 | discardComments: true, 186 | }, 187 | }), 188 | ], 189 | }; 190 | } 191 | if (buildType === MODERN_CONFIG) { 192 | return { 193 | minimizer: [new TerserPlugin(configureTerser())], 194 | }; 195 | } 196 | }; 197 | 198 | // Configure Postcss loader 199 | const configurePostcssLoader = (buildType) => { 200 | if (buildType === LEGACY_CONFIG) { 201 | return { 202 | test: /\.(css)$/, 203 | use: [ 204 | MiniCssExtractPlugin.loader, 205 | { 206 | loader: require.resolve("css-loader"), 207 | options: { 208 | importLoaders: 2, 209 | sourceMap: true, 210 | }, 211 | }, 212 | { 213 | loader: require.resolve("resolve-url-loader"), 214 | }, 215 | { 216 | loader: require.resolve("postcss-loader"), 217 | options: { 218 | sourceMap: true, 219 | }, 220 | }, 221 | ], 222 | }; 223 | } 224 | // Don't generate CSS for the modern config in production 225 | if (buildType === MODERN_CONFIG) { 226 | return { 227 | test: /\.(pcss|css)$/, 228 | loader: require.resolve("ignore-loader"), 229 | }; 230 | } 231 | }; 232 | 233 | // Configure terser 234 | const configureTerser = () => { 235 | return { 236 | cache: true, 237 | parallel: true, 238 | sourceMap: true, 239 | }; 240 | }; 241 | 242 | // Production module exports 243 | module.exports = [ 244 | merge(common.legacyConfig, { 245 | output: { 246 | filename: path.join("./js", "[name]-legacy.[chunkhash].js"), 247 | }, 248 | mode: "production", 249 | devtool: "source-map", 250 | optimization: configureOptimization(LEGACY_CONFIG), 251 | module: { 252 | rules: [ 253 | configurePostcssLoader(LEGACY_CONFIG), 254 | configureImageLoader(LEGACY_CONFIG), 255 | configureSvgLoader(LEGACY_CONFIG), 256 | ], 257 | }, 258 | plugins: [ 259 | new MiniCssExtractPlugin({ 260 | path: path.resolve(settings.paths.working, settings.paths.dist.base), 261 | filename: path.join("./css", "[name].[chunkhash].css"), 262 | }), 263 | new CompressionPlugin(configureCompression()), 264 | ], 265 | }, projectConfig), 266 | merge(common.modernConfig, { 267 | output: { 268 | filename: path.join("./js", "[name].[chunkhash].js"), 269 | }, 270 | mode: "production", 271 | devtool: "source-map", 272 | optimization: configureOptimization(MODERN_CONFIG), 273 | module: { 274 | rules: [ 275 | configurePostcssLoader(MODERN_CONFIG), 276 | configureImageLoader(MODERN_CONFIG), 277 | configureSvgLoader(LEGACY_CONFIG), 278 | ], 279 | }, 280 | plugins: [ 281 | new ImageminWebpWebpackPlugin(), 282 | new CompressionPlugin(configureCompression()), 283 | ], 284 | }, projectConfig), 285 | ]; 286 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Contributors][contributors-shield]][contributors-url] 2 | [![Forks][forks-shield]][forks-url] 3 | [![Stargazers][stars-shield]][stars-url] 4 | [![Issues][issues-shield]][issues-url] 5 | [![MIT License][license-shield]][license-url] 6 | [![LinkedIn][linkedin-shield]][linkedin-url] 7 | 8 | 9 |
10 |

11 | 12 | Logo 13 | 14 | 15 |

Batch Craft Webpack

16 | 17 |

18 | A zero config Webpack package made for Craft CMS 19 |
20 | Explore the docs » 21 |
22 |
23 | Report Bug 24 | · 25 | Request Feature 26 |

27 |

28 | 29 | 30 | ## Table of Contents 31 | 32 | * [About the Project](#about-the-project) 33 | * [Made For](#made-for) 34 | * [Features](#features) 35 | * [Getting Started](#getting-started) 36 | * [Prerequisites](#prerequisites) 37 | * [Installation](#installation) 38 | * [Usage](#usage) 39 | * [Roadmap](#roadmap) 40 | * [Contributing](#contributing) 41 | * [License](#license) 42 | * [Contact](#contact) 43 | * [Acknowledgements](#acknowledgements) 44 | 45 | 46 | ## About The Project 47 | 48 | This package provides a quick and easy way to get setup with a fully featured Webpack setup. 49 | 50 | [![Product Name Screen Shot][product-screenshot]](https://github.com/batchnz/craft-webpack) 51 | 52 | We've tailored this package to our own requirements based on developing a multitude of Craft CMS projects ranging from simple to complex. It was originally based on the fine work by [nystudio107](https://github.com/nystudio107/annotated-webpack-4-config/) and refined into this package. 53 | 54 | ### Made For 55 | 56 | * Craft CMS (with Twigpack) 57 | * VueJS 58 | 59 | ### Features 60 | 61 | * [Webpack 4](https://webpack.js.org/) 62 | * [Core JS 3](https://github.com/zloirock/core-js) 63 | * [Vue Loader](https://vue-loader.vuejs.org/) 64 | * [Post CSS](https://postcss.org/) 65 | * Manifest Configuration for use with [Twigpack](https://github.com/nystudio107/craft-twigpack) 66 | * [State Preserving Hot Reload](https://vue-loader.vuejs.org/guide/hot-reload.html) 67 | * Build and Development Pipelines 68 | * [Source Maps](https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/) 69 | * [Modern/Legacy Builds using Browserlist](https://browserl.ist/) 70 | * [Compression](https://github.com/google/zopfli) 71 | * [ESLint](https://eslint.org/) 72 | * [Prettier](https://prettier.io/) 73 | 74 | 75 | 76 | ## Getting Started 77 | 78 | To get a local copy up and running follow these simple steps. 79 | 80 | 81 | ### Installation 82 | 83 | 1. Install the package 84 | ```sh 85 | npm install @batch/craft-webpack -D 86 | ``` 87 | 2. Add the build scripts to your project's package.json 88 | ```json 89 | "scripts": { 90 | "dev": "webpack-dev-server --progress --hide-modules --config=node_modules/@batch/craft-webpack/webpack.dev.js", 91 | "build": "export NODE_ENV=production && webpack --progress --hide-modules --config=node_modules/@batch/craft-webpack/webpack.prod.js", 92 | "dev-legacy": "webpack-dev-server --progress --hide-modules --config=node_modules/@batch/craft-webpack/webpack.dev.js --env.BUILD_TYPE=legacy", 93 | "dev-combined": "webpack-dev-server --progress --hide-modules --config=node_modules/@batch/craft-webpack/webpack.dev.js --env.BUILD_TYPE=combined" 94 | } 95 | ``` 96 | 97 | 98 | ## Usage 99 | 100 | This package provides two pipelines out of the box, one for development and one for production builds. 101 | 102 | Development builds default to using the `modern` config and will only output transpiled JS based on the [modernBrowsers](https://github.com/batchnz/craft-webpack/blob/master/package.json#L31) browserlist config defined in package.json. To test in legacy browers (e.g. IE11) you can use the `legacy` config (see below) which uses the [legacyBrowsers](https://github.com/batchnz/craft-webpack/blob/master/package.json#L40) browserlist config. A third mode, `combined`, will serve modern and legacy build artifacts. 103 | 104 | 105 | ### Development 106 | 107 | Runs the development pipeline. It'll create a local webserver using webpack-dev-server to serve the assets. 108 | 109 | ```sh 110 | npm run dev 111 | ``` 112 | 113 | If you want to use the legacy or combined build types during development, you use the `dev-legacy` or `dev-combined` tasks 114 | 115 | ### Production 116 | 117 | Runs the production pipeline. This will compress the build artifacts ready for sites in production. 118 | 119 | ```sh 120 | npm run build 121 | ``` 122 | 123 | ## Configuration 124 | 125 | ### Settings 126 | 127 | The settings in webpack.settings.js can be overridden at a project level by placing a webpack.settings.js file in the project root. This will be merged with the base webpack.settings.js file during build. 128 | 129 | eg. 130 | 131 | ```javascript 132 | module.exports = { 133 | paths: { 134 | src: { 135 | base: "./resources/", 136 | css: "./resources/css/", 137 | js: "./resources/js/" 138 | } 139 | } 140 | }; 141 | ``` 142 | 143 | ### Webpack Config 144 | 145 | Any custom Webpack config can be included by adding a webpack.config.js in the project root. This will be merged with the final Webpack config during build. 146 | 147 | eg. 148 | 149 | ```javascript 150 | const path = require("path"); 151 | 152 | module.exports = { 153 | resolve: { 154 | alias: { 155 | "@": path.resolve(__dirname, "src/vue/") 156 | } 157 | } 158 | }; 159 | ``` 160 | 161 | ### ESLint Config 162 | 163 | The base ESLint config is setup with the following: 164 | - airbnb/base 165 | - vue/recommended 166 | - prettier/recommended 167 | 168 | By default, ESLint will lint \*.js, \*.vue files in the `src` directory. To change any of the [ESLint plugin options](https://eslint.org/docs/developer-guide/nodejs-api#%E2%97%86-new-eslint-options) simply update the `eslintConfig` key in your `webpack.settings.js` override file. 169 | 170 | Alternatively, to just set the linting rules, you can drop an `.eslintrc.js` file as per normal usage in your site root folder. 171 | 172 | ### Source Maps** 173 | 174 | Out of the box, source maps are generated using webpack's "eval-source-map" devtool, which provides a "true" source map to the original code which makes debugging using breakpoints straightforward. 175 | 176 | Other [devtools for webpack](https://webpack.js.org/configuration/devtool) are available which have different performance and accuracy characteristics. You can easily override the chosen devtool by adding a `devtool` option to your webpack.config.js overrides. 177 | 178 | 179 | ## Roadmap 180 | 181 | See the [open issues](https://github.com/batchnz/craft-webpack/issues) for a list of proposed features (and known issues). 182 | 183 | 184 | ## Contributing 185 | 186 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 187 | 188 | 1. Fork the Project 189 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 190 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 191 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 192 | 5. Open a Pull Request 193 | 194 | 195 | ## License 196 | 197 | Distributed under the MIT License. See `LICENSE` for more information. 198 | 199 | 200 | ## Contact 201 | 202 | * Josh Smith - [@batchnz](https://twitter.com/batchnz) - josh@batch.nz 203 | * Jude Reid - [@batchnz](https://twitter.com/batchnz) - jude@batch.nz 204 | 205 | Project Link: [https://github.com/batchnz/craft-webpack](https://github.com/batchnz/craft-webpack) 206 | 207 | 208 | ## Acknowledgements 209 | 210 | * [nystudio107 annotated webpack config](https://github.com/nystudio107/annotated-webpack-4-config/) 211 | * [nystudio107 twigpack](https://github.com/nystudio107/craft-twigpack) 212 | 213 | 214 | 215 | [contributors-shield]: https://img.shields.io/github/contributors/batchnz/craft-webpack.svg?style=flat-square 216 | [contributors-url]: https://github.com/batchnz/craft-webpack/graphs/contributors 217 | [forks-shield]: https://img.shields.io/github/forks/batchnz/craft-webpack.svg?style=flat-square 218 | [forks-url]: https://github.com/batchnz/craft-webpack/network/members 219 | [stars-shield]: https://img.shields.io/github/stars/batchnz/craft-webpack.svg?style=flat-square 220 | [stars-url]: https://github.com/batchnz/craft-webpack/stargazers 221 | [issues-shield]: https://img.shields.io/github/issues/batchnz/craft-webpack.svg?style=flat-square 222 | [issues-url]: https://github.com/batchnz/craft-webpack/issues 223 | [license-shield]: https://img.shields.io/github/license/batchnz/craft-webpack.svg?style=flat-square 224 | [license-url]: https://github.com/batchnz/craft-webpack/blob/master/LICENSE 225 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555 226 | [linkedin-url]: https://www.linkedin.com/company/batchnz/ 227 | [product-screenshot]: images/screenshot.jpg 228 | --------------------------------------------------------------------------------