我正在将旧的 gulp 项目迁移到 webpack,并想知道是否可以创建一个不需要
entry
和 output
选项的 webpack 项目,我有一系列需要执行的任务,但是我实际上没有输入和输出选项的数据。
我的意思是……这是一个典型的 webpack 项目
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
mode: isProduction ? 'production' : 'development',
entry: {
main: './src/index.js', // Adjust for your JS entry
},
output: {
path: path.resolve(__dirname, 'build'),
filename: 'js/[name].[contenthash].js',
},
...
但是我不需要输入或输出,因为我的任务会处理这些,我该如何解决这个问题?
这是完整代码
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
const handlebars = require('handlebars');
require('dotenv').config();
// Register Handlebars helpers
handlebars.registerHelper('concat', (a, b) => a + b);
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
mode: isProduction ? 'production' : 'development',
entry: {
main: './src/index.js', // Adjust for your JS entry
},
output: {
path: path.resolve(__dirname, 'build'),
filename: 'js/[name].[contenthash].js',
},
devtool: isProduction ? 'source-map' : 'eval-source-map',
devServer: {
static: {
directory: path.join(__dirname, 'build'),
},
port: 7777,
hot: true,
open: true,
},
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer')({
overrideBrowserslist: [
'IE >= 11',
'Edge >= 12',
'Firefox >= 50',
'Chrome >= 55',
'Safari >= 8',
],
grid: true,
}),
],
},
},
},
'sass-loader',
],
},
{
test: /\.hbs$/,
use: [
{
loader: 'handlebars-loader',
options: {
helperDirs: path.resolve(__dirname, 'src/helpers'),
partialDirs: path.resolve(__dirname, 'src/partials'),
},
},
],
},
{
test: /\.(png|jpg|gif|svg|json)$/,
type: 'asset/resource',
generator: {
filename: 'assets/[hash][ext][query]',
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.hbs',
inject: 'body',
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
}),
new CopyWebpackPlugin({
patterns: [
{
from: './src/data/**/*.json',
to: 'data/[name][ext]',
transform(content) {
const json = JSON.parse(content.toString());
if (json.someValue) {
delete json.otherValue;
}
return Buffer.from(JSON.stringify(json, null, 2));
},
},
],
}),
new webpack.DefinePlugin({
'process.env': JSON.stringify(process.env),
}),
],
optimization: {
minimize: isProduction,
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin({
terserOptions: {
format: {
comments: false,
},
},
extractComments: false,
}),
],
splitChunks: {
chunks: 'all',
},
},
};
};
这是我要转换的代码
const gulp = require('gulp'),
sass = require('gulp-sass'),
sourcemaps = require('gulp-sourcemaps'),
browserSync = require('browser-sync').create(),
handlebars = require('gulp-compile-handlebars'),
HandlebarsFilter = require("handlebars"),
Filter = require("handlebars.filter"),
layouts = require('handlebars-layouts'),
fs = require('fs'),
sassLint = require('gulp-sass-lint'),
autoprefixer = require('gulp-autoprefixer'),
cleanCSS = require('gulp-clean-css'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
clean = require('gulp-clean'),
merge = require('gulp-merge-json');
const rename = require('gulp-rename')
const jsonConcat = require('gulp-json-concat')
const json_refs = require('gulp-json-refs')
const htmlhint = require("gulp-htmlhint")
const Contentstack = require('contentstack')
const fetch = require('node-fetch')
require('dotenv').config()
Filter.registerHelper(HandlebarsFilter);
global.fetch = fetch
global.Headers = fetch.Headers
const Stack = new Contentstack.Stack({
'api_key': process.env.CONTENTSTACK_API_KEY,
'delivery_token': process.env.CONTENTSTACK_DELIVERY_TOKEN,
'environment': process.env.CONTENTSTACK_ENVIRONMENT,
"region": Contentstack.Region.EU
})
// const Stack = new Contentstack.Stack({
// 'api_key': 'blt066259863543a0d2',
// 'delivery_token': 'csda6aeec93af5a25133f03add',
// 'environment': 'candidate',
// "region": Contentstack.Region.EU
// })
handlebars.Handlebars.registerHelper(layouts(handlebars.Handlebars));
handlebars.Handlebars.registerHelper('concat', function (a, b) {
return a + b
})
handlebars.Handlebars.registerHelper('ifvalue', function (conditional, options) {
if (options.hash.value === conditional) {
return options.fn(this)
} else {
return options.inverse(this)
}
})
handlebars.Handlebars.registerHelper('ifEquals', function (arg1, arg2, options) {
return (arg1 == arg2) ? options.fn(this) : options.inverse(this);
});
handlebars.Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
switch (operator) {
case '==':
return (v1 == v2) ? options.fn(this) : options.inverse(this);
case '===':
return (v1 === v2) ? options.fn(this) : options.inverse(this);
case '!=':
return (v1 != v2) ? options.fn(this) : options.inverse(this);
case '!==':
return (v1 !== v2) ? options.fn(this) : options.inverse(this);
case '<':
return (v1 < v2) ? options.fn(this) : options.inverse(this);
case '<=':
return (v1 <= v2) ? options.fn(this) : options.inverse(this);
case '>':
return (v1 > v2) ? options.fn(this) : options.inverse(this);
case '>=':
return (v1 >= v2) ? options.fn(this) : options.inverse(this);
case '&&':
return (v1 && v2) ? options.fn(this) : options.inverse(this);
case '||':
return (v1 || v2) ? options.fn(this) : options.inverse(this);
default:
return options.inverse(this);
}
});
gulp.task('JSON:build', () =>
gulp.src([
'./src/data/*.json',
'./src/data/**/*.json'
])
.pipe(json_refs())
.pipe(merge({
fileName: 'data.json',
edit: (parsedJson, file) => {
if (parsedJson.someValue) {
delete parsedJson.otherValue
}
return parsedJson
},
}))
.pipe(gulp.dest('./build/data'))
)
gulp.task('css:lint', () =>
gulp.src(['node_modules/my-css/src/scss/**/*.scss', './src/scss/**/*.scss', '!node_modules/my-css/src/scss/libs/**'])
.pipe(sassLint())
.pipe(sassLint.format())
.pipe(sassLint.failOnError())
)
gulp.task('css:build', gulp.series(() =>
gulp.src(['node_modules/my-css/src/scss/**/*.scss', './src/scss/**/*.scss', '!node_modules/my-css/src/scss/libs/**'])
.pipe(sourcemaps.init({largeFile: true}))
.pipe(sass({
outputStyle: 'expanded',
includePaths: ['./node_modules', './node_modules/my-css/src/scss']
}))
.on('error', handleError)
.pipe(autoprefixer({
overrideBrowserslist: ['IE >= 11', 'Edge >= 12', 'Firefox >= 50', 'Chrome >= 55', 'Safari >= 8'],
add: true,
grid: true
}))
.pipe(cleanCSS({rebase: false}))
.pipe(sourcemaps.write('./maps'))
.pipe(gulp.dest('build/css/'))
.pipe(browserSync.stream())
))
gulp.task('js:build-libs', () =>
gulp.src([
'./src/js/libs/!(jquery.min)*.js'
], {
allowEmpty: true
})
.on('error', handleError)
.pipe(concat('libs.min.js'))
.pipe(gulp.dest('build/js/'))
)
gulp.task('js:build-emulation', () =>
gulp.src([
'node_modules/my-css/build/js/emulation.min.js'
])
.on('error', handleError)
.pipe(concat('emulation.min.js'))
.pipe(uglify({}))
.pipe(gulp.dest('build/js/'))
)
gulp.task('js:build-jquery', () =>
gulp.src([
'node_modules/my-css/build/js/jquery.min.js'
])
.on('error', handleError)
.pipe(concat('jquery.min.js'))
.pipe(gulp.dest('build/js/'))
)
gulp.task('js:build-components', () =>
gulp.src([
'src/js/helpers/*.js',
'./src/js/components/*.js'
])
.on('error', handleError)
.pipe(concat('components.min.js'))
.pipe(uglify({}))
.pipe(gulp.dest('build/js/'))
)
gulp.task('js:concat', gulp.series('js:build-libs', 'js:build-components', () =>
gulp.src(['node_modules/my-css/build/js/bundle.min.js', './build/js/libs.min.js', './build/js/components.min.js'], {
allowEmpty: true
})
.on('error', handleError)
.pipe(concat('bundle.min.js'))
.pipe(gulp.dest('build/js/'))
))
gulp.task('js:build', gulp.series('js:concat', 'js:build-emulation', 'js:build-jquery', () =>
gulp.src([
'node_modules/my-css/src/js/**/*.js',
'./src/js/**/*.js',
'!node_modules/my-css/src/js/libs/*.js',
'!node_modules/my-css/src/js/helpers/*.js',
'!node_modules/my-css/src/js/emulation/*.js',
'!node_modules/my-css/src/js/components/*.js'
])
.on('error', handleError)
.pipe(gulp.dest('build/js/'))
.pipe(browserSync.stream())
))
gulp.task('html:validate', () => {
return gulp.src('src/**/*.html')
.pipe(htmlhint({
"id-unique": false,
"doctype-first": false,
"attr-value-double-quotes": false,
"title-require": false,
"attr-lowercase": false,
"attr-no-duplication": false,
"spec-char-escape": false,
"src-not-empty": false
}))
.pipe(htmlhint.failOnError())
})
gulp.task('html:build', gulp.series("css:build", 'js:build', 'html:validate', () =>
gulp.src(['node_modules/my-css/src/*.html', './src/old-templates/*.html', './src/old-templates/price-event/*.html', './src/old-templates/tradepro/*.html',
'./src/old-templates/worktops/*.html', './src/old-templates/kitchens/*.html', './src/old-templates/bathrooms/*.html', './src/old-templates/get-the-look/*.html',
'./src/old-templates/how-to-guides/*.html', './src/old-templates/latest-offers/*.html', './src/old-templates/buying-guides/*.html', './src/old-templates/help-and-advice/*.html'])
.pipe(handlebars(JSON.parse(fs.readFileSync('./build/data/data.json')), {
batch: ['node_modules/my-css/src/layouts', 'node_modules/my-css/src/partials', 'node_modules/my-css/src/components', 'node_modules/my-css/src/elements',
"./src/layouts", "./src/partials", "./src/components", "./src/elements"]
}))
.on('error', handleError)
.pipe(gulp.dest('build/'))
.pipe(browserSync.stream())
))
gulp.task('html-template:join', (cb) => {
gulp.src(['./src/template-data/**/*.json', "!./src/template-data/**/common/**"])
.pipe(json_refs({
loaderOptions: {
prepareRequest: function (req, callback) {
req.set('x-api-key', 'abc123')
//req.auth('my-username', 'my-password');
console.log("URL: " + req.url)
console.log(req)
callback(undefined, req)
}
}
}))
.pipe(jsonConcat('data.json', function (data) {
return Buffer.from(JSON.stringify(data));
}))
.pipe(gulp.dest('./build/templated-data-combined/'))
.on('end', cb)
})
function addToCategories(categories, categoryName, subCategoryName, title, url) {
let currentValues = categories[categoryName];
const entry = {
"url": url,
"title": title
}
if (subCategoryName) {
if (typeof currentValues === 'undefined') {
//no previous entry for this category and we're creating a sub cat (e.g. /kitchen/specialRange/green. Create a whole new top level
const parentEntry = {
"url": "",
"title": subCategoryName,
"subCategoryLinks": [entry]
}
categories[categoryName] = [parentEntry];
} else {
//we've already got an entry for the top level, so lets find the subCategory if it exists
const matchingIndex = categories[categoryName].findIndex(item => item.title === subCategoryName);
if (matchingIndex >= 0) {
const subItem = categories[categoryName][matchingIndex];
//we might have a previously defined item - check whether there is a subCategoryLinks property
if (subItem.hasOwnProperty("subCategoryLinks")) {
//add to the list
subItem.subCategoryLinks.push(entry);
} else {
//create new list
subItem.subCategoryLinks = [entry];
}
} else {
//There is no existing entry for this sub category (e.g. /kitchen exists, but not /specialRange- create one
const parentEntry = {
"url": "",
"title": subCategoryName,
"subCategoryLinks": [entry]
}
categories[categoryName].push(parentEntry);
}
}
} else {
if (typeof currentValues === 'undefined') {
categories[categoryName] = [entry];
} else {
//check whether we already have a sub category in place
const matchingIndex = categories[categoryName].findIndex(item => item.title === title);
if (matchingIndex >= 0) {
//we already have an entry for this, so lets just update the URL
categories[categoryName][matchingIndex].url = url
} else {
categories[categoryName].push(entry)
}
}
}
}
gulp.task('html-template:build', gulp.series('html-template:join', 'css:build', 'js:build', () => {
const templateData = JSON.parse(fs.readFileSync('./build/templated-data-combined/data.json'))
for (let key in templateData) {
const pageData = templateData[key]
gulp.src('./src/templates/' + pageData.config.template)
.pipe(handlebars(pageData, {
batch: ['node_modules/my-css/src/layouts', 'node_modules/my-css/src/partials', 'node_modules/my-css/src/components', 'node_modules/my-css/src/elements',
"./src/layouts", "./src/partials", "./src/components", "./src/elements"]
}))
.on('error', handleError)
.pipe(rename(pageData.config.prototypeFile))
.pipe(gulp.dest('./build/'))
.pipe(rename(pageData.config.hybrisPath + ".html"))
.pipe(gulp.dest('./build/'))
.pipe(browserSync.stream())
}
//build homepage listing
let categories = {}
for (let key in templateData) {
const pageConfig = templateData[key].config
const titleElements = pageConfig.prototypeTitle.split("/")
if (titleElements.length === 1) {
//no category
addToCategories(categories, "Uncategorised", null, titleElements[0], pageConfig.hybrisPath)
} else if (titleElements.length === 2) {
//got a category
addToCategories(categories, titleElements[0], null, titleElements[1], pageConfig.hybrisPath)
} else if (titleElements.length === 3) {
addToCategories(categories, titleElements[0], titleElements[1], titleElements[2], pageConfig.hybrisPath)
} else {
console.error("Too many sub categories in " + pageConfig.prototypeTitle)
}
}
const model = {"categories": categories};
return gulp.src('./src/templates/proto_content_listing_page.hbs')
.pipe(handlebars(model, {
batch: ['node_modules/my-css/src/layouts', 'node_modules/my-css/src/partials', 'node_modules/my-css/src/components', 'node_modules/my-css/src/elements',
"./src/layouts", "./src/partials", "./src/components", "./src/elements"]
}))
.on('error', handleError)
.pipe(rename("contentListingPage.html"))
.pipe(gulp.dest('./build/'))
.pipe(browserSync.stream())
}))
const processAllRecords = async (contentType, references) => {
let Query = Stack.ContentType(contentType).Query();
let findMore = true
let recordsRetrieved = 0
while (findMore) {
const results = await Query
.includeCount()
.includeReference(references)
.skip(recordsRetrieved)
.toJSON()
.find();
console.log(`${contentType} - call to contentshack returned ${results.count} articles`);
if (results.length) {
const result = results[0]
console.log(`${contentType} - call to contentshack returned ${result.count} articles`);
if (result.length === 0) {
findMore = false
} else {
for (let innerKey in result) {
recordsRetrieved = recordsRetrieved + 1
console.log('Create Page:',result[innerKey].hybris_content_mapping.html_file_name)
console.log('Create Page:',result[innerKey].hybris_content_mapping.hybris_path + '.html')
gulp.src('./src/cms/templates/' + result[innerKey].template)
.pipe(handlebars(result[innerKey], {
batch: ['./src/layouts',
'node_modules/my-css/src/layouts',
'node_modules/my-css/src/partials',
'node_modules/my-css/src/components',
'node_modules/my-css/src/elements',
'./src/cms/components']
}))
.on('error', handleError)
.pipe(rename(result[innerKey].hybris_content_mapping.html_file_name))
.pipe(gulp.dest('./build/'))
.pipe(rename(result[innerKey].hybris_content_mapping.hybris_path + '.html'))
.pipe(gulp.dest('./build/'))
.pipe(browserSync.stream());
}
}
} else {
console.log('no results found')
}
}
console.log(`${contentType} - Retrieved ${recordsRetrieved} articles from ContentShack`)
}
gulp.task('cms:homepage:build', async (done) => {
await processAllRecords(
'primary_landing_page',
['reference_usp', 'reference_general_banner_top', 'reference_inspiration_banner', 'reference_general_banner_bottom', 'reference_recall_banner']
);
done()
}
)
gulp.task('cms:build', gulp.series('cms:homepage:build', () =>
gulp.src('build/js/temp', {
read: false,
allowEmpty: true
})
));
gulp.task('robots:build', () =>
gulp.src('node_modules/my-css/src/robots.txt')
.on('error', handleError)
.pipe(gulp.dest('build/'))
)
gulp.task('build', gulp.series('JSON:build', 'html:build', 'robots:build', 'html-template:build', 'cms:build', () =>
gulp.src('build/js/temp', {read: false, allowEmpty: true})
// .pipe(clean())
))
gulp.task('local:build', gulp.series('html:build', 'robots:build', 'html-template:build', 'cms:build', () =>
gulp.src('build/js/temp', {read: false, allowEmpty: true})
))
gulp.task('webserver', () =>
browserSync.init({
server: {
baseDir: "./build",
serveStaticOptions: {
extensions: ["html"]
}
},
port: 7777,
ui: {
port: 7778
}
})
)
gulp.task('watch', gulp.series('webserver', function () {
gulp.watch(['./src/**/*.html', './src/**/*.hbs', './src/**/*.json'], ['html:build'])
gulp.watch('./src/scss/**/*.scss', ['css:build'])
gulp.watch('./src/js/**/*.js', ['js:build'])
gulp.watch('./src/js/**/*.js', ['js:build'])
gulp.watch('./src/template*/**/*.*', ['html-template:build'])
gulp.watch('./src/cms/**/*.*', ['cms:build'])
// gulp.watch('./src/data/*.json', ['JSON:build']);
}))
gulp.task('clean', () =>
gulp.src('build/', {
read: false,
allowEmpty: true
})
.pipe(clean())
)
gulp.task('default', gulp.series('build', 'webserver', 'watch'))
function handleError(err) {
console.log(err.toString())
this.emit('end')
console.error("An error occurred during the build - exiting.....")
throw Error(err)
}
您可以看到我是如何安排工作的。我尝试过谷歌搜索解决方案,也只是删除它们,但它给了我错误。如果有人想引导我朝正确的方向前进,任何帮助将不胜感激
使用现代的 html-bundler-webpack-plugin 您的配置可以更短。
捆绑器插件取代了许多插件和加载器的功能:
使用捆绑器插件,您不需要
entry
webpack 选项。
该条目是一个模板。源 SCSS 和 JS 文件应直接在模板中定义。
例如。 ./src/index.hbs
:
<html>
<head>
<!-- relative path to favicon source file -->
<link href="./favicon.ico" rel="icon" />
<!--
relative path to SCSS source file
OR import SCSS file in the ./src/index.js to enable HMR for CSS
-->
<link href="./style.scss" rel="stylesheet" />
</head>
<body>
<h1>Hello World!</h1>
<!-- relative path to image source file -->
<img src="./picture.png" />
<!-- relative path to JS source file -->
<script src="./index.js" defer="defer"></script>
</body>
</html>
const path = require('path');
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
require('dotenv').config();
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
mode: isProduction ? 'production' : 'development',
output: {
path: path.resolve(__dirname, 'build'),
clean: true, // <= do the same as clean-webpack-plugin
},
devtool: isProduction ? 'source-map' : 'eval-source-map',
devServer: {
static: {
directory: path.join(__dirname, 'build'),
},
port: 7777,
hot: true,
open: true,
},
module: {
rules: [
{
test: /\.scss$/,
use: [
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['IE >= 11', 'Edge >= 12', 'Firefox >= 50', 'Chrome >= 55', 'Safari >= 8'],
grid: true,
}),
],
},
},
},
'sass-loader',
],
},
{
test: /\.(png|jpg|gif|svg|json)$/,
type: 'asset/resource',
generator: {
filename: 'assets/[hash][ext][query]',
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
plugins: [
new HtmlBundlerPlugin({
entry: {
// define pages here
index: 'src/index.hbs', // save generated HTML into build/index.html
},
js: {
filename: 'js/[name].[contenthash:8].js', // JS output filename
},
css: {
filename: 'css/[name].[contenthash:8].css', // CSS output filename
hot: true, // enable hot update CSS in browser without a full reload
},
preprocessor: 'handlebars', // use handlebars compiler for *.hbs templates
preprocessorOptions: {
// an array of relative or absolute paths to helpers, define helpers manually as an object
// TODO: add your custom helper as separate file into the helpers directory
// 'concat', (a, b) => a + b)
helpers: [path.resolve(__dirname, 'src/helpers')],
// an array of relative or absolute paths to partials
partials: [path.resolve(__dirname, 'src/partials')],
},
}),
new CopyWebpackPlugin({
patterns: [
{
from: './src/data/**/*.json',
to: 'data/[name][ext]',
transform(content) {
const json = JSON.parse(content.toString());
if (json.someValue) {
delete json.otherValue;
}
return Buffer.from(JSON.stringify(json, null, 2));
},
},
],
}),
new webpack.DefinePlugin({
'process.env': JSON.stringify(process.env),
}),
],
optimization: {
// sass-loader self minimize CSS in production mode
// html-bundler-webpack-plugin avoid extraction of comments
splitChunks: {
cacheGroups: {
scripts: {
test: /\.(js|ts)$/, // <= IMPORTANT: split only script files
chunks: 'all', // <= define this option only under the cache group
},
},
},
},
};
};
需要使用
output
选项来定义自定义输出目录output.path
,默认为./dist
。
您可以定义 clean-webpack-plugin
选项,而不是 output.clean: true
。
在 GitHub 上创建一个存储库,我可以帮助您配置您的项目。