woocommerce/plugins/woocommerce-blocks/bin/webpack-helpers.js

618 lines
17 KiB
JavaScript

/**
* External dependencies
*/
const path = require( 'path' );
const MergeExtractFilesPlugin = require( './merge-extract-files-webpack-plugin' );
const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );
const ProgressBarPlugin = require( 'progress-bar-webpack-plugin' );
const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' );
const { NormalModuleReplacementPlugin } = require( 'webpack' );
const WebpackRTLPlugin = require( 'webpack-rtl-plugin' );
const chalk = require( 'chalk' );
const { omit } = require( 'lodash' );
const NODE_ENV = process.env.NODE_ENV || 'development';
const dashIconReplacementModule = path.resolve(
__dirname,
'../assets/js/module_replacements/dashicon.js'
);
function findModuleMatch( module, match ) {
if ( module.request && match.test( module.request ) ) {
return true;
} else if ( module.issuer ) {
return findModuleMatch( module.issuer, match );
}
return false;
}
const requestToExternal = ( request ) => {
const wcDepMap = {
'@woocommerce/blocks-registry': [ 'wc', 'wcBlocksRegistry' ],
'@woocommerce/settings': [ 'wc', 'wcSettings' ],
'@woocommerce/block-data': [ 'wc', 'wcBlocksData' ],
'@woocommerce/shared-context': [ 'wc', 'wcSharedContext' ],
};
if ( wcDepMap[ request ] ) {
return wcDepMap[ request ];
}
};
const requestToHandle = ( request ) => {
const wcHandleMap = {
'@woocommerce/blocks-registry': 'wc-blocks-registry',
'@woocommerce/settings': 'wc-settings',
'@woocommerce/block-settings': 'wc-settings',
'@woocommerce/block-data': 'wc-blocks-data-store',
'@woocommerce/shared-context': 'wc-shared-context',
};
if ( wcHandleMap[ request ] ) {
return wcHandleMap[ request ];
}
};
const getAlias = ( options = {} ) => {
let { pathPart } = options;
pathPart = pathPart ? `${ pathPart }/` : '';
return {
'@woocommerce/atomic-blocks': path.resolve(
__dirname,
`../assets/js/${ pathPart }atomic/blocks`
),
'@woocommerce/atomic-utils': path.resolve(
__dirname,
`../assets/js/${ pathPart }atomic/utils`
),
'@woocommerce/base-components': path.resolve(
__dirname,
`../assets/js/${ pathPart }base/components/`
),
'@woocommerce/base-context': path.resolve(
__dirname,
`../assets/js/${ pathPart }base/context/`
),
'@woocommerce/base-hocs': path.resolve(
__dirname,
`../assets/js/${ pathPart }base/hocs/`
),
'@woocommerce/base-hooks': path.resolve(
__dirname,
`../assets/js/${ pathPart }base/hooks/`
),
'@woocommerce/base-utils': path.resolve(
__dirname,
`../assets/js/${ pathPart }base/utils/`
),
'@woocommerce/block-components': path.resolve(
__dirname,
`../assets/js/${ pathPart }components/`
),
'@woocommerce/block-hocs': path.resolve(
__dirname,
`../assets/js/${ pathPart }hocs`
),
'@woocommerce/blocks-registry': path.resolve(
__dirname,
'../assets/js/blocks-registry'
),
'@woocommerce/block-settings': path.resolve(
__dirname,
'../assets/js/settings/blocks'
),
'@woocommerce/icons': path.resolve( __dirname, `../assets/js/icons` ),
'@woocommerce/resource-previews': path.resolve(
__dirname,
`../assets/js/${ pathPart }previews/`
),
'@woocommerce/e2e-tests': path.resolve(
__dirname,
'node_modules/woocommerce/tests/e2e-tests'
),
};
};
const stableMainEntry = {
// Shared blocks code
blocks: './assets/js/index.js',
// @wordpress/components styles
'custom-select-control-style':
'./node_modules/wordpress-components/src/custom-select-control/style.scss',
'spinner-style':
'./node_modules/wordpress-components/src/spinner/style.scss',
'snackbar-notice-style':
'./node_modules/wordpress-components/src/snackbar/style.scss',
// Styles for grid blocks. WP <=5.2 doesn't have the All Products block,
// so this file would not be included if not explicitly declared here.
// This file is excluded from the default build so CSS styles are included
// in the other the components are imported.
'product-list-style': './assets/js/base/components/product-list/style.scss',
// Blocks
'handpicked-products': './assets/js/blocks/handpicked-products/index.js',
'product-best-sellers': './assets/js/blocks/product-best-sellers/index.js',
'product-category': './assets/js/blocks/product-category/index.js',
'product-categories': './assets/js/blocks/product-categories/index.js',
'product-new': './assets/js/blocks/product-new/index.js',
'product-on-sale': './assets/js/blocks/product-on-sale/index.js',
'product-top-rated': './assets/js/blocks/product-top-rated/index.js',
'products-by-attribute':
'./assets/js/blocks/products-by-attribute/index.js',
'featured-product': './assets/js/blocks/featured-product/index.js',
'all-reviews': './assets/js/blocks/reviews/all-reviews/index.js',
'reviews-by-product':
'./assets/js/blocks/reviews/reviews-by-product/index.js',
'reviews-by-category':
'./assets/js/blocks/reviews/reviews-by-category/index.js',
'product-search': './assets/js/blocks/product-search/index.js',
'product-tag': './assets/js/blocks/product-tag/index.js',
'featured-category': './assets/js/blocks/featured-category/index.js',
'all-products': './assets/js/blocks/products/all-products/index.js',
'price-filter': './assets/js/blocks/price-filter/index.js',
'attribute-filter': './assets/js/blocks/attribute-filter/index.js',
'active-filters': './assets/js/blocks/active-filters/index.js',
'block-error-boundary':
'./assets/js/base/components/block-error-boundary/style.scss',
cart: './assets/js/blocks/cart-checkout/cart/index.js',
checkout: './assets/js/blocks/cart-checkout/checkout/index.js',
};
const experimentalMainEntry = {
'single-product': './assets/js/blocks/single-product/index.js',
};
const mainEntry =
// env variables are strings, so we compare against a string, so we need to parse it.
parseInt( process.env.WOOCOMMERCE_BLOCKS_PHASE, 10 ) < 3
? stableMainEntry
: { ...stableMainEntry, ...experimentalMainEntry };
const stableFrontEndEntry = {
reviews: './assets/js/blocks/reviews/frontend.js',
'all-products': './assets/js/blocks/products/all-products/frontend.js',
'price-filter': './assets/js/blocks/price-filter/frontend.js',
'attribute-filter': './assets/js/blocks/attribute-filter/frontend.js',
'active-filters': './assets/js/blocks/active-filters/frontend.js',
cart: './assets/js/blocks/cart-checkout/cart/frontend.js',
checkout: './assets/js/blocks/cart-checkout/checkout/frontend.js',
};
const experimentalFrontEndEntry = {
'single-product': './assets/js/blocks/single-product/frontend.js',
};
const frontEndEntry =
// env variables are strings, so we compare against a string, so we need to parse it.
parseInt( process.env.WOOCOMMERCE_BLOCKS_PHASE, 10 ) < 3
? stableFrontEndEntry
: { ...stableFrontEndEntry, ...experimentalFrontEndEntry };
const getEntryConfig = ( main = true, exclude = [] ) => {
const entryConfig = main ? mainEntry : frontEndEntry;
return omit( entryConfig, exclude );
};
const getMainConfig = ( options = {} ) => {
let { fileSuffix } = options;
const { alias, resolvePlugins = [] } = options;
fileSuffix = fileSuffix ? `-${ fileSuffix }` : '';
const resolve = alias
? {
alias,
plugins: resolvePlugins,
}
: {
plugins: resolvePlugins,
};
return {
entry: getEntryConfig( true, options.exclude || [] ),
output: {
devtoolNamespace: 'wc',
path: path.resolve( __dirname, '../build/' ),
filename: `[name]${ fileSuffix }.js`,
library: [ 'wc', 'blocks', '[name]' ],
libraryTarget: 'this',
// This fixes an issue with multiple webpack projects using chunking
// overwriting each other's chunk loader function.
// See https://webpack.js.org/configuration/output/#outputjsonpfunction
jsonpFunction: 'webpackWcBlocksJsonp',
},
optimization: {
splitChunks: {
minSize: 0,
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
enforce: true,
},
editor: {
// Capture all `editor` stylesheets and the components stylesheets.
test: ( module = {} ) =>
module.constructor.name === 'CssModule' &&
( findModuleMatch( module, /editor\.scss$/ ) ||
findModuleMatch(
module,
/[\\/]assets[\\/]components[\\/]/
) ),
name: 'editor',
chunks: 'all',
priority: 10,
},
'vendors-style': {
test: /\/node_modules\/.*?style\.s?css$/,
name: 'vendors-style',
chunks: 'all',
priority: 7,
},
style: {
test: /style\.scss$/,
name: 'style',
chunks: 'all',
priority: 5,
},
},
},
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader?cacheDirectory',
options: {
presets: [ '@wordpress/babel-preset-default' ],
plugins: [
NODE_ENV === 'production'
? require.resolve(
'babel-plugin-transform-react-remove-prop-types'
)
: false,
require.resolve(
'@babel/plugin-proposal-class-properties'
),
].filter( Boolean ),
},
},
},
{
test: /\/node_modules\/.*?style\.s?css$/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { importLoaders: 1 } },
'postcss-loader',
{
loader: 'sass-loader',
query: {
includePaths: [ 'node_modules' ],
data: [
'colors',
'breakpoints',
'variables',
'mixins',
'animations',
'z-index',
]
.map(
( imported ) =>
`@import "~@wordpress/base-styles/${ imported }";`
)
.join( ' ' ),
},
},
],
},
{
test: /\.s?css$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { importLoaders: 1 } },
'postcss-loader',
{
loader: 'sass-loader',
query: {
includePaths: [
'assets/css/abstracts',
'node_modules',
],
data: [
'_colors',
'_variables',
'_breakpoints',
'_mixins',
]
.map(
( imported ) =>
`@import "${ imported }";`
)
.join( ' ' ),
},
},
],
},
],
},
plugins: [
new WebpackRTLPlugin( {
filename: `[name]${ fileSuffix }-rtl.css`,
minify: {
safe: true,
},
} ),
new MiniCssExtractPlugin( {
filename: `[name]${ fileSuffix }.css`,
} ),
new MergeExtractFilesPlugin(
[
`build/editor${ fileSuffix }.js`,
`build/style${ fileSuffix }.js`,
],
`build/vendors${ fileSuffix }.js`
),
new ProgressBarPlugin( {
format:
chalk.blue( 'Build' ) +
' [:bar] ' +
chalk.green( ':percent' ) +
' :msg (:elapsed seconds)',
} ),
new DependencyExtractionWebpackPlugin( {
injectPolyfill: true,
requestToExternal,
requestToHandle,
} ),
new NormalModuleReplacementPlugin(
/dashicon/,
( result ) => ( result.resource = dashIconReplacementModule )
),
],
resolve,
};
};
const getFrontConfig = ( options = {} ) => {
let { fileSuffix } = options;
const { alias, resolvePlugins = [] } = options;
fileSuffix = fileSuffix ? `-${ fileSuffix }` : '';
const resolve = alias
? {
alias,
plugins: resolvePlugins,
}
: {
plugins: resolvePlugins,
};
return {
entry: getEntryConfig( false, options.exclude || [] ),
output: {
devtoolNamespace: 'wc',
path: path.resolve( __dirname, '../build/' ),
filename: `[name]-frontend${ fileSuffix }.js`,
// This fixes an issue with multiple webpack projects using chunking
// overwriting each other's chunk loader function.
// See https://webpack.js.org/configuration/output/#outputjsonpfunction
jsonpFunction: 'webpackWcBlocksJsonp',
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader?cacheDirectory',
options: {
presets: [
[
'@babel/preset-env',
{
modules: false,
targets: {
browsers: [
'extends @wordpress/browserslist-config',
],
},
},
],
],
plugins: [
require.resolve(
'@babel/plugin-proposal-object-rest-spread'
),
require.resolve(
'@babel/plugin-transform-react-jsx'
),
require.resolve(
'@babel/plugin-proposal-async-generator-functions'
),
require.resolve(
'@babel/plugin-transform-runtime'
),
require.resolve(
'@babel/plugin-proposal-class-properties'
),
NODE_ENV === 'production'
? require.resolve(
'babel-plugin-transform-react-remove-prop-types'
)
: false,
].filter( Boolean ),
},
},
},
{
test: /\.s[c|a]ss$/,
use: {
loader: 'ignore-loader',
},
},
],
},
plugins: [
new ProgressBarPlugin( {
format:
chalk.blue( 'Build frontend scripts' ) +
' [:bar] ' +
chalk.green( ':percent' ) +
' :msg (:elapsed seconds)',
} ),
new DependencyExtractionWebpackPlugin( {
injectPolyfill: true,
requestToExternal,
requestToHandle,
} ),
new NormalModuleReplacementPlugin(
/dashicon/,
( result ) => ( result.resource = dashIconReplacementModule )
),
],
resolve,
};
};
const getPaymentMethodsExtensionConfig = ( options = {} ) => {
const { alias, resolvePlugins = [] } = options;
const resolve = alias
? {
alias,
plugins: resolvePlugins,
}
: {
plugins: resolvePlugins,
};
return {
entry: {
'wc-payment-method-stripe':
'./assets/js/payment-method-extensions/payment-methods/stripe/index.js',
'wc-payment-method-cheque':
'./assets/js/payment-method-extensions/payment-methods/cheque/index.js',
'wc-payment-method-paypal':
'./assets/js/payment-method-extensions/payment-methods/paypal/index.js',
},
output: {
devtoolNamespace: 'wc',
path: path.resolve( __dirname, '../build/' ),
filename: `[name].js`,
// This fixes an issue with multiple webpack projects using chunking
// overwriting each other's chunk loader function.
// See https://webpack.js.org/configuration/output/#outputjsonpfunction
jsonpFunction: 'webpackWcBlocksPaymentMethodExtensionJsonp',
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader?cacheDirectory',
options: {
presets: [
[
'@babel/preset-env',
{
modules: false,
targets: {
browsers: [
'extends @wordpress/browserslist-config',
],
},
},
],
],
plugins: [
require.resolve(
'@babel/plugin-proposal-object-rest-spread'
),
require.resolve(
'@babel/plugin-transform-react-jsx'
),
require.resolve(
'@babel/plugin-proposal-async-generator-functions'
),
require.resolve(
'@babel/plugin-transform-runtime'
),
require.resolve(
'@babel/plugin-proposal-class-properties'
),
NODE_ENV === 'production'
? require.resolve(
'babel-plugin-transform-react-remove-prop-types'
)
: false,
].filter( Boolean ),
},
},
},
{
test: /\.s?css$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { importLoaders: 1 } },
'postcss-loader',
{
loader: 'sass-loader',
query: {
includePaths: [
'assets/css/abstracts',
'node_modules',
],
data: [
'_colors',
'_variables',
'_breakpoints',
'_mixins',
]
.map(
( imported ) =>
`@import "${ imported }";`
)
.join( ' ' ),
},
},
],
},
],
},
plugins: [
new WebpackRTLPlugin( {
filename: `[name]-rtl.css`,
minify: {
safe: true,
},
} ),
new MiniCssExtractPlugin( {
filename: `[name].css`,
} ),
new ProgressBarPlugin( {
format:
chalk.blue( 'Build payment method extension scripts' ) +
' [:bar] ' +
chalk.green( ':percent' ) +
' :msg (:elapsed seconds)',
} ),
new DependencyExtractionWebpackPlugin( {
injectPolyfill: true,
requestToExternal,
requestToHandle,
} ),
new NormalModuleReplacementPlugin(
/dashicon/,
( result ) => ( result.resource = dashIconReplacementModule )
),
],
resolve,
};
};
module.exports = {
getAlias,
getFrontConfig,
getMainConfig,
getPaymentMethodsExtensionConfig,
};