Skip to content

Instantly share code, notes, and snippets.

@wpscholar
Last active March 14, 2022 10:21
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save wpscholar/261141cf7b2bf4efd45cb86ad0a43ff2 to your computer and use it in GitHub Desktop.
Save wpscholar/261141cf7b2bf4efd45cb86ad0a43ff2 to your computer and use it in GitHub Desktop.
Webpack 4 Config for WordPress plugin, theme, and block development
**/*.min.js
**/*.build.js
**/node_modules/**
**/vendor/**
build
coverage
cypress
node_modules
vendor
{
"root": true,
"parser": "babel-eslint",
"extends": [
"wordpress",
"plugin:react/recommended",
"plugin:jsx-a11y/recommended",
"plugin:jest/recommended"
],
"env": {
"browser": false,
"es6": true,
"node": true,
"mocha": true,
"jest/globals": true
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"globals": {
"wp": true,
"wpApiSettings": true,
"window": true,
"document": true
},
"plugins": [
"react",
"jsx-a11y",
"jest"
],
"settings": {
"react": {
"pragma": "wp"
}
},
"rules": {
"array-bracket-spacing": [
"error",
"always"
],
"brace-style": [
"error",
"1tbs"
],
"camelcase": [
"error",
{
"properties": "never"
}
],
"comma-dangle": [
"error",
"always-multiline"
],
"comma-spacing": "error",
"comma-style": "error",
"computed-property-spacing": [
"error",
"always"
],
"constructor-super": "error",
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": "error",
"func-call-spacing": "error",
"indent": [
"error",
"tab",
{
"SwitchCase": 1
}
],
"jsx-a11y/label-has-for": [
"error",
{
"required": "id"
}
],
"jsx-a11y/media-has-caption": "off",
"jsx-a11y/no-noninteractive-tabindex": "off",
"jsx-a11y/role-has-required-aria-props": "off",
"jsx-quotes": "error",
"key-spacing": "error",
"keyword-spacing": "error",
"lines-around-comment": "off",
"no-alert": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-console": "error",
"no-const-assign": "error",
"no-debugger": "error",
"no-dupe-args": "error",
"no-dupe-class-members": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-duplicate-imports": "error",
"no-else-return": "error",
"no-eval": "error",
"no-extra-semi": "error",
"no-fallthrough": "error",
"no-lonely-if": "error",
"no-mixed-operators": "error",
"no-mixed-spaces-and-tabs": "error",
"no-multiple-empty-lines": [
"error",
{
"max": 1
}
],
"no-multi-spaces": "error",
"no-multi-str": "off",
"no-negated-in-lhs": "error",
"no-nested-ternary": "error",
"no-redeclare": "error",
"no-restricted-syntax": [
"error",
{
"selector": "ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]",
"message": "Path access on WordPress dependencies is not allowed."
},
{
"selector": "ImportDeclaration[source.value=/^blocks$/]",
"message": "Use @wordpress/blocks as import path instead."
},
{
"selector": "ImportDeclaration[source.value=/^components$/]",
"message": "Use @wordpress/components as import path instead."
},
{
"selector": "ImportDeclaration[source.value=/^date$/]",
"message": "Use @wordpress/date as import path instead."
},
{
"selector": "ImportDeclaration[source.value=/^editor$/]",
"message": "Use @wordpress/editor as import path instead."
},
{
"selector": "ImportDeclaration[source.value=/^element$/]",
"message": "Use @wordpress/element as import path instead."
},
{
"selector": "ImportDeclaration[source.value=/^i18n$/]",
"message": "Use @wordpress/i18n as import path instead."
},
{
"selector": "ImportDeclaration[source.value=/^data$/]",
"message": "Use @wordpress/data as import path instead."
},
{
"selector": "ImportDeclaration[source.value=/^utils$/]",
"message": "Use @wordpress/utils as import path instead."
},
{
"selector": "CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])",
"message": "Translate function arguments must be string literals."
},
{
"selector": "CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])",
"message": "Translate function arguments must be string literals."
},
{
"selector": "CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])",
"message": "Translate function arguments must be string literals."
}
],
"no-shadow": "error",
"no-undef": "error",
"no-undef-init": "error",
"no-unreachable": "error",
"no-unsafe-negation": "error",
"no-unused-expressions": "error",
"no-unused-vars": "error",
"no-useless-computed-key": "error",
"no-useless-constructor": "error",
"no-useless-return": "error",
"no-var": "error",
"no-whitespace-before-property": "error",
"object-curly-spacing": [
"error",
"always"
],
"prefer-const": "error",
"quote-props": [
"error",
"as-needed"
],
"react/display-name": "off",
"react/jsx-curly-spacing": [
"error",
{
"when": "never",
"children": true
}
],
"react/jsx-equals-spacing": "error",
"react/jsx-indent": [
"error",
"tab"
],
"react/jsx-indent-props": [
"error",
"tab"
],
"react/jsx-key": "error",
"react/jsx-tag-spacing": "error",
"react/no-children-prop": "off",
"react/no-find-dom-node": "warn",
"react/prop-types": "off",
"semi": "error",
"semi-spacing": "error",
"space-before-blocks": [
"error",
"always"
],
"space-before-function-paren": [
"error",
"never"
],
"space-in-parens": [
"error",
"always"
],
"space-infix-ops": [
"error",
{
"int32Hint": false
}
],
"space-unary-ops": [
"error"
],
"template-curly-spacing": [
"error",
"always"
],
"valid-jsdoc": [
"error",
{
"requireReturn": false
}
],
"valid-typeof": "error",
"yoda": "off"
}
}
{
"name": "my-thing",
"description": "A description of my thing.",
"author": "Micah Wood <micah@wpscholar.com> (http://wpscholar.com)",
"license": "proprietary",
"private": true,
"scripts": {
"build": "cross-env NODE_ENV=production webpack",
"lint": "eslint build/js/**/*",
"start": "webpack --watch",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"classnames": "^2.2.5"
},
"devDependencies": {
"@babel/core": "^7.1.2",
"@wordpress/babel-plugin-makepot": "^2.1.1",
"@wordpress/babel-preset-default": "^3.0.0",
"@wordpress/browserslist-config": "^2.1.3",
"@wordpress/i18n": "^3.0.0",
"ajv": "^6.5.4",
"autoprefixer": "^9.1.5",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.4",
"babel-plugin-transform-class-properties": "^6.24.1",
"cross-env": "^5.0.1",
"css-loader": "^1.0.0",
"eslint": "^5.6.1",
"eslint-config-wordpress": "^2.0.0",
"eslint-plugin-jest": "^21.6.1",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.5.1",
"eslint-plugin-wordpress": "^0.1.0",
"file-loader": "^2.0.0",
"import-glob": "^1.5.0",
"mini-css-extract-plugin": "^0.4.0",
"node-sass": "^4.9.0",
"node-sass-glob-importer": "^5.1.2",
"postcss-loader": "^3.0.0",
"raw-loader": "^0.5.1",
"sass-loader": "^7.0.1",
"style-loader": "^0.23.0",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.1"
}
}
'use strict';
const autoprefixer = require( 'autoprefixer' );
const fs = require( 'fs' );
const globImporter = require( 'node-sass-glob-importer' );
const browsers = require( '@wordpress/browserslist-config' );
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );
const path = require( 'path' );
const webpack = require( 'webpack' );
module.exports = function() {
const mode = process.env.NODE_ENV || 'development';
const extensionPrefix = mode === 'production' ? '.min' : '';
// This is the URL path relative to the root domain.
const publicPath = '/wp-content/mu-plugins/blocks/';
// These are the paths where different types of resources should end up.
const paths = {
css: 'assets/css/',
img: 'assets/img/',
font: 'assets/font/',
js: 'assets/js/',
lang: 'languages/',
};
// The property names will be the file names, the values are the files that should be included.
const entry = {
blocks: [
'./build/scss/blocks.scss',
],
editor: [
'./build/js/editor.js',
'./build/scss/editor.scss',
],
};
const loaders = {
css: {
loader: 'css-loader',
options: {
sourceMap: true,
},
},
postCss: {
loader: 'postcss-loader',
options: {
plugins: [
autoprefixer( {
browsers,
flexbox: 'no-2009',
} ),
],
sourceMap: true,
},
},
sass: {
loader: 'sass-loader',
options: {
importer: globImporter(),
sourceMap: true,
},
},
};
const config = {
mode,
entry,
output: {
path: path.join( __dirname, '/' ),
publicPath,
filename: `${ paths.js }[name]${ extensionPrefix }.js`,
},
externals: {
'@wordpress/a11y': 'wp.a11y',
'@wordpress/components': 'wp.components', // Not really a package.
'@wordpress/blocks': 'wp.blocks', // Not really a package.
'@wordpress/data': 'wp.data', // Not really a package.
'@wordpress/date': 'wp.date', // Not really a package.
'@wordpress/element': 'wp.element', // Not really a package.
'@wordpress/hooks': 'wp.hooks',
'@wordpress/i18n': 'wp.i18n',
'@wordpress/utils': 'wp.utils', // Not really a package
backbone: 'Backbone',
jquery: 'jQuery',
lodash: 'lodash',
moment: 'moment',
react: 'React',
'react-dom': 'ReactDOM',
tinymce: 'tinymce',
},
module: {
rules: [
{
enforce: 'pre',
test: /\.js|.jsx/,
loader: 'import-glob',
exclude: /(node_modules)/,
},
{
test: /\.js|.jsx/,
loader: 'babel-loader',
query: {
presets: [
'@wordpress/default',
],
plugins: [
[
'@wordpress/babel-plugin-makepot',
{
'output': `${ paths.lang }translation.pot`,
}
],
'transform-class-properties',
],
},
exclude: /(node_modules|bower_components)/,
},
{
test: /\.html$/,
loader: 'raw-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
loaders.css,
loaders.postCss,
],
exclude: /node_modules/,
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
loaders.css,
loaders.postCss,
loaders.sass,
],
exclude: /node_modules/,
},
{
test: /\.(ttf|eot|svg|woff2?)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: paths.font,
},
},
],
exclude: /(assets)/,
},
],
},
plugins: [
new MiniCssExtractPlugin( {
filename: `${ paths.css }[name]${ extensionPrefix }.css`,
} ),
new webpack.DefinePlugin( {
'process.env.NODE_ENV': JSON.stringify( mode ),
} ),
function() {
// Custom webpack plugin - remove generated JS files that aren't needed
this.hooks.done.tap( 'webpack', function( stats ) {
stats.compilation.chunks.forEach( chunk => {
if ( ! chunk.entryModule._identifier.includes( '.js' ) ) {
chunk.files.forEach( file => {
if ( file.includes( '.js' ) ) {
fs.unlinkSync( path.join( __dirname, `/${ file }` ) );
}
} );
}
} );
} );
},
],
};
if ( mode !== 'production' ) {
config.devtool = 'source-map';
}
return config;
};
@wpscholar
Copy link
Author

@jackjwilliams A new file will be created for each entry point. You can have as many entry points as you want. See https://gist.github.com/wpscholar/261141cf7b2bf4efd45cb86ad0a43ff2#file-webpack-config-js-L29

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment