При попытке построить проект создаются два HTML-файла, т.е. (dist / test.html и dist / src / index.html)
dist / test.html
/dist/src/index.html?88827167<script type="text/javascript"
src="/assets/vendors.chunk.js"></script>
<script type="text/javascript" src="/assets/client.js"></script>
dist / src / index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
</html>
webpack.config.js
import fs from 'fs';
import path from 'path';
import webpack from 'webpack';
import WebpackAssetsManifest from 'webpack-assets-manifest';
import nodeExternals from 'webpack-node-externals';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import WriteFilePlugin from 'write-file-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import overrideRules from './lib/overrideRules';
import pkg from '../package.json';
const ROOT_DIR = path.resolve(__dirname, '..');
const resolvePath = (...args) => path.resolve(ROOT_DIR, ...args);
const SRC_DIR = resolvePath('src');
const BUILD_DIR = resolvePath('build');
const isDebug = !process.argv.includes('--release');
const isVerbose = process.argv.includes('--verbose');
const isAnalyze =
process.argv.includes('--analyze') || process.argv.includes('--analyse');
const reScript = /\.(js|jsx|mjs)$/;
const reStyle = /\.(css|less|styl|scss|sass|sss)$/;
const reImage = /\.(bmp|gif|jpg|jpeg|png|svg)$/;
const staticAssetName = isDebug
? '[path][name].[ext]?[hash:8]'
: '[hash:8].[ext]';
// CSS Nano options http://cssnano.co/
const minimizeCssOptions = {
discardComments: { removeAll: true },
};
//
// Common configuration chunk to be used for both
// client-side (client.js) and server-side (server.js) bundles
// -----------------------------------------------------------------------------
const config = {
context: ROOT_DIR,
mode: isDebug ? 'development' : 'production',
output: {
path: resolvePath(BUILD_DIR, 'public/assets'),
publicPath: '/assets/',
pathinfo: isVerbose,
filename: isDebug ? '[name].js' : '[name].[chunkhash:8].js',
chunkFilename: isDebug
? '[name].chunk.js'
: '[name].[chunkhash:8].chunk.js',
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
},
resolve: {
// Allow absolute paths in imports, e.g. import Button from 'components/Button'
// Keep in sync with .flowconfig and .eslintrc
modules: ['node_modules', 'src'],
},
module: {
// Make missing exports an error instead of warning
strictExportPresence: true,
rules: [
// Rules for JS / JSX
{
test: reScript,
include: [SRC_DIR, resolvePath('tools')],
loader: 'babel-loader',
options: {
// https://github.com/babel/babel-loader#options
cacheDirectory: isDebug,
// https://babeljs.io/docs/usage/options/
babelrc: false,
configFile: false,
presets: [
// A Babel preset that can automatically determine the Babel plugins and polyfills
// https://github.com/babel/babel-preset-env
[
'@babel/preset-env',
{
targets: {
browsers: pkg.browserslist,
},
forceAllTransforms: !isDebug, // for UglifyJS
modules: false,
useBuiltIns: false,
debug: false,
},
],
// Flow
// https://github.com/babel/babel/tree/master/packages/babel-preset-flow
'@babel/preset-flow',
// JSX
// https://github.com/babel/babel/tree/master/packages/babel-preset-react
['@babel/preset-react', { development: isDebug }],
],
plugins: [
// Experimental ECMAScript proposals
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import',
// Treat React JSX elements as value types and hoist them to the highest scope
// https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-constant-elements
...(isDebug ? [] : ['@babel/transform-react-constant-elements']),
// Replaces the React.createElement function with one that is more optimized for production
// https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-inline-elements
...(isDebug ? [] : ['@babel/transform-react-inline-elements']),
// Remove unnecessary React propTypes from the production build
// https://github.com/oliviertassinari/babel-plugin-transform-react-remove-prop-types
...(isDebug ? [] : ['transform-react-remove-prop-types']),
],
},
},
// Rules for Style Sheets
{
test: reStyle,
rules: [
// Convert CSS into JS module
{
issuer: { not: [reStyle] },
use: 'isomorphic-style-loader',
},
// Process external/third-party styles
{
exclude: SRC_DIR,
loader: 'css-loader',
options: {
sourceMap: isDebug,
minimize: isDebug ? false : minimizeCssOptions,
},
},
// Process internal/project styles (from src folder)
{
include: SRC_DIR,
loader: 'css-loader',
options: {
// CSS Loader https://github.com/webpack/css-loader
importLoaders: 1,
sourceMap: isDebug,
// CSS Modules https://github.com/css-modules/css-modules
modules: true,
localIdentName: isDebug
? '[name]-[local]-[hash:base64:5]'
: '[hash:base64:5]',
// CSS Nano http://cssnano.co/
minimize: isDebug ? false : minimizeCssOptions,
},
},
// Apply PostCSS plugins including autoprefixer
{
loader: 'postcss-loader',
options: {
config: {
path: './tools/postcss.config.js',
},
},
},
// Compile Less to CSS
// https://github.com/webpack-contrib/less-loader
// Install dependencies before uncommenting: yarn add --dev less-loader less
// {
// test: /\.less$/,
// loader: 'less-loader',
// },
// Compile Sass to CSS
// https://github.com/webpack-contrib/sass-loader
// Install dependencies before uncommenting: yarn add --dev sass-loader node-sass
// {
// test: /\.(scss|sass)$/,
// loader: 'sass-loader',
// },
],
},
// Rules for images
{
test: reImage,
oneOf: [
// Inline lightweight images into CSS
{
issuer: reStyle,
oneOf: [
// Inline lightweight SVGs as UTF-8 encoded DataUrl string
{
test: /\.svg$/,
loader: 'svg-url-loader',
options: {
name: staticAssetName,
limit: 4096, // 4kb
},
},
// Inline lightweight images as Base64 encoded DataUrl string
{
loader: 'url-loader',
options: {
name: staticAssetName,
limit: 4096, // 4kb
},
},
],
},
// Or return public URL to image resource
{
loader: 'file-loader',
options: {
name: staticAssetName,
},
},
],
},
// Convert plain text into JS module
{
test: /\.txt$/,
loader: 'raw-loader',
},
// Convert Markdown into HTML
{
test: /\.md$/,
loader: path.resolve(__dirname, './lib/markdown-loader.js'),
},
// Return public URL for all assets unless explicitly excluded
// DO NOT FORGET to update `exclude` list when you adding a new loader
{
exclude: [reScript, reStyle, reImage, /\.json$/, /\.txt$/, /\.md$/],
loader: 'file-loader',
options: {
name: staticAssetName,
},
},
// Exclude dev modules from production build
...(isDebug
? []
: [
{
test: resolvePath(
'node_modules/react-deep-force-update/lib/index.js',
),
loader: 'null-loader',
},
]),
],
},
// Don't attempt to continue if there are any errors.
bail: !isDebug,
cache: isDebug,
// Specify what bundle information gets displayed
// https://webpack.js.org/configuration/stats/
stats: {
cached: isVerbose,
cachedAssets: isVerbose,
chunks: isVerbose,
chunkModules: isVerbose,
colors: true,
hash: isVerbose,
modules: isVerbose,
reasons: isDebug,
timings: true,
version: isVerbose,
},
// Choose a developer tool to enhance debugging
// https://webpack.js.org/configuration/devtool/#devtool
devtool: isDebug ? 'cheap-module-inline-source-map' : 'source-map',
};
//
// Configuration for the client-side bundle (client.js)
// -----------------------------------------------------------------------------
const clientConfig = {
...config,
name: 'client',
target: 'web',
entry: {
client: ['@babel/polyfill', './src/client.js'],
},
plugins: [
// Forces webpack-dev-server program to write bundle files to the file system.
new WriteFilePlugin(),
new HtmlWebpackPlugin({
inject: 'body',
template: './src/index.html',
filename: 'test.html',
minify: false,
title: 'React Kit',
}),
// Define free variables
// https://webpack.js.org/plugins/define-plugin/
new webpack.DefinePlugin({
'process.env.BROWSER': true,
__DEV__: isDebug,
}),
// Emit a file with assets paths
// https://github.com/webdeveric/webpack-assets-manifest#options
new WebpackAssetsManifest({
output: `${BUILD_DIR}/asset-manifest.json`,
publicPath: true,
writeToDisk: true,
customize: ({ key, value }) => {
// You can prevent adding items to the manifest by returning false.
if (key.toLowerCase().endsWith('.map')) return false;
return { key, value };
},
done: (manifest, stats) => {
// Write chunk-manifest.json.json
const chunkFileName = `${BUILD_DIR}/chunk-manifest.json`;
try {
const fileFilter = file => !file.endsWith('.map');
const addPath = file => manifest.getPublicPath(file);
const chunkFiles = stats.compilation.chunkGroups.reduce((acc, c) => {
acc[c.name] = [
...(acc[c.name] || []),
...c.chunks.reduce(
(files, cc) => [
...files,
...cc.files.filter(fileFilter).map(addPath),
],
[],
),
];
return acc;
}, Object.create(null));
fs.writeFileSync(chunkFileName, JSON.stringify(chunkFiles, null, 2));
} catch (err) {
console.error(`ERROR: Cannot write ${chunkFileName}: `, err);
if (!isDebug) process.exit(1);
}
},
}),
...(isDebug
? []
: [
// Webpack Bundle Analyzer
// https://github.com/th0r/webpack-bundle-analyzer
...(isAnalyze ? [new BundleAnalyzerPlugin()] : []),
]),
],
// Move modules that occur in multiple entry chunks to a new entry chunk (the commons chunk).
optimization: {
splitChunks: {
cacheGroups: {
commons: {
chunks: 'initial',
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
},
},
},
},
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
// https://webpack.js.org/configuration/node/
// https://github.com/webpack/node-libs-browser/tree/master/mock
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
};
//
// Configuration for the server-side bundle (server.js)
// -----------------------------------------------------------------------------
const serverConfig = {
...config,
name: 'server',
target: 'node',
entry: {
server: ['@babel/polyfill', './src/server.js'],
},
output: {
...config.output,
path: BUILD_DIR,
filename: '[name].js',
chunkFilename: 'chunks/[name].js',
libraryTarget: 'commonjs2',
},
// Webpack mutates resolve object, so clone it to avoid issues
// https://github.com/webpack/webpack/issues/4817
resolve: {
...config.resolve,
},
module: {
...config.module,
rules: overrideRules(config.module.rules, rule => {
// Override babel-preset-env configuration for Node.js
if (rule.loader === 'babel-loader') {
return {
...rule,
options: {
...rule.options,
presets: rule.options.presets.map(preset =>
preset[0] !== '@babel/preset-env'
? preset
: [
'@babel/preset-env',
{
targets: {
node: pkg.engines.node.match(/(\d+\.?)+/)[0],
},
modules: false,
useBuiltIns: false,
debug: false,
},
],
),
},
};
}
// Override paths to static assets
if (
rule.loader === 'file-loader' ||
rule.loader === 'url-loader' ||
rule.loader === 'svg-url-loader'
) {
return {
...rule,
options: {
...rule.options,
emitFile: false,
},
};
}
return rule;
}),
},
externals: [
'./chunk-manifest.json',
'./asset-manifest.json',
nodeExternals({
whitelist: [reStyle, reImage],
}),
],
plugins: [
// Define free variables
// https://webpack.js.org/plugins/define-plugin/
new webpack.DefinePlugin({
'process.env.BROWSER': false,
__DEV__: isDebug,
}),
// Adds a banner to the top of each generated chunk
// https://webpack.js.org/plugins/banner-plugin/
new webpack.BannerPlugin({
banner: 'require("source-map-support").install();',
raw: true,
entryOnly: false,
}),
],
// Do not replace node globals with polyfills
// https://webpack.js.org/configuration/node/
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
},
};
export default [clientConfig, serverConfig];
Вопрос- Почему существует два HTML-файла, один из которых имеет ссылку на другой, создаваемый?
html-webpack-plugin: ^ 3.2.0