Webpack 现在非常火爆!Webpack 在模块打包和使用 Vue 或 React 等框架方面表现出色,但在处理静态资源(如 CSS)时则显得有些笨拙。您可能更习惯使用 Gulp 之类的工具来处理静态资源,并且这样做有一些很好的理由。
尽管如此,我们的静态项目中的 JavaScript 代码量正在不断增长,因此,为了弥补这一点,让我们在保持使用 Gulp 的同时,也利用 Webpack。在本文中,我们将具体使用 Gulp 4。我们将使用现代技术构建易于维护的工作流程,包括功能强大且实用的 **热模块替换** (HMR)。

您可能希望从此处开始
本文不完全适合初学者。如果您不熟悉 Webpack 或 Gulp,也许可以先学习以下教程。
Gulp 教程
Webpack 教程
演示
在 GitHub 上查看演示仓库。“hmr” 分支展示了如何设置热模块替换。
先决条件
运行以下命令以安装必要的包
npm install babel-core \
babel-preset-es2015 \
browser-sync \
gulpjs/gulp#4.0 \
webpack \
webpack-dev-middleware \
webpack-hot-middleware -D
从 Node v7.9.0 开始,ES6 模块 不受支持,这就是我们安装 Babel 以便在我们的任务中使用 import 语句和其他前沿 JS 功能的原因。
如果您不需要 HMR,可以随意将热中间件从上面列出的包中删除。开发中间件不依赖于它。
起始点
让我们开始吧!在项目的根目录中创建一个名为 tasks
的文件夹,并在其中创建三个文件:index.js
、webpack.js
和 server.js
。由于 index 文件充当 gulpfile.js
,而 webpack 文件充当 webpack.config.js
,因此我们的项目根目录中的文件更少,看起来更整洁。
site
文件夹包含您网站的所有资源
╔ site
║ ╚═══ main.js
╠ tasks
║ ╠═══ index.js
║ ╠═══ server.js
║ ╚═══ webpack.js
╚ package.json
为了告诉 Gulp 任务位于何处,我们需要在 `package.json` 中添加标志
"scripts": {
"dev": "gulp --require babel-register --gulpfile tasks",
"build": "NODE_ENV=production gulp build --require babel-register --gulpfile tasks"
}
babel-register
命令处理 import 语句,而 --gulpfile
标志定义 gulpfile.js
或在我们的例子中 index.js
的路径。我们只需要引用 tasks
文件夹,因为就像在 HTML 中一样,名为 index 的文件标记了入口点。
设置基本的Webpack配置
在 `webpack.js` 中
import path from 'path'
import webpack from 'webpack'
let config = {
entry: './main.js',
output: {
filename: './bundle.js',
path: path.resolve(__dirname, '../site')
},
context: path.resolve(__dirname, '../site')
}
function scripts() {
return new Promise(resolve => webpack(config, (err, stats) => {
if (err) console.log('Webpack', err)
console.log(stats.toString({ /* stats options */ }))
resolve()
}))
}
module.exports = { config, scripts }
请注意,我们不像许多教程那样直接导出对象,而是先将其放入变量中。这是必要的,以便我们可以在下面的 Gulp 任务 scripts
中以及下一步中的服务器中间件中使用此配置。
上下文
config.context
设置对于将所有路径相对于 site
文件夹设置是必要的。否则,它们将从 tasks
文件夹开始,这可能会导致以后出现混乱。
分离配置和任务
如果您有一个非常长的 Webpack 配置,您也可以将其与任务拆分为两个文件。
// webpack.js
export let config = { /* ... */ }
// scripts.js
import { config } from './webpack'
export function scripts() { /* ... */ }
热模块替换
以下是使 HMR 工作的方法。更改入口和插件
entry: {
main: [
'./main.js',
'webpack/hot/dev-server',
'webpack-hot-middleware/client'
]
},
/* ... */
plugins: [
new webpack.HotModuleReplacementPlugin()
]
确保为生产环境禁用额外的入口和 HMR 插件。Webpack Merge 包有助于为开发和生产环境设置不同的环境。
BrowserSync
现在设置 BrowserSync 任务
import gulp from 'gulp'
import Browser from 'browser-sync'
import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'
import { config as webpackConfig } from './webpack'
const browser = Browser.create()
const bundler = webpack(webpackConfig)
export function server() {
let config = {
server: 'site',
middleware: [
webpackDevMiddleware(bundler, { /* options */ }),
webpackHotMiddleware(bundler)
],
}
browser.init(config)
gulp.watch('site/*.js').on('change', () => browser.reload())
}
**开发中间件** 使 BrowserSync 能够处理在 webpack.js
中定义的入口。为了向其提供此信息,我们导入了配置模块。另一方面,**热中间件** 检查 app 组件(例如 Vue.js 的 `.vue` 文件)中的更改以注入。
由于我们无法热重载 main.js
等文件,因此我们对其进行 **监视**,并在更改时重新加载窗口。同样,如果您不需要 HMR,请删除 **webpackHotMiddleware**。
导入所有任务
`index.js` 文件包含所有任务
import gulp from 'gulp'
import { scripts } from './webpack'
import { server } from './server'
export const dev = gulp.series( server )
export const build = gulp.series( scripts )
export default dev
导出的变量定义了在哪个命令下运行哪些任务。默认导出使用 gulp
运行。
如果您为 Webpack 分离了开发和生产环境,您可能希望运行一个 gulp build
任务,该任务利用生产选项。为此,我们单独导入 scripts
任务,因为我们不需要在此处启动服务器。
在开发过程中,Webpack 由 BrowserSync 运行,因此在 dev 命令中放置 scripts 任务没有必要。
正在运行的任务
要开始开发,您不能仅仅运行gulp
或gulp build
,因为它会在项目根目录中查找gulpfile.js
。我们必须运行npm命令npm run dev
和npm run build
来使用定义的标志。
扩展
现在您可以想象扩展和编写更多任务是多么容易。在一个文件中导出任务,并在index.js
中导入它。干净且易于维护!
为了让您了解如何设置项目文件夹,这是我的个人设置
╔ build
╠ src
╠ tasks
║ ╠═══ config.js => project wide
║ ╠═══ icons.js => optimize/concat SVG
║ ╠═══ images.js => optimize images
║ ╠═══ index.js => run tasks
║ ╠═══ misc.js => copy, delete
║ ╠═══ server.js => start dev server
║ ╠═══ styles.js => CSS + preprocessor
║ ╚═══ webpack.js
╚ package.json
再次,为什么要同时使用Webpack和Gulp?
静态文件处理
Gulp 比 Webpack 更擅长处理静态资源。 Copy Webpack 插件 也可以将文件从源文件夹复制到构建文件夹,但是当涉及到监视文件删除或更改(例如覆盖图像)时,gulp.watch
是更安全的选择。
服务器环境
Webpack 还通过 Webpack Dev Server 提供本地服务器环境,但使用 BrowserSync 有一些你可能不想错过的功能
- 针对非应用程序项目的 CSS/HTML/图像注入
- 开箱即用的多设备测试
- 包含一个管理面板以进行更多控制
- 带宽限制,用于速度和加载测试
编译时间
如 Git 上的这篇文章Hub 中所述,Sass 由 node-sass 处理的速度比 Webpack 的 sass-loader、css-loader 和 extract-text-webpack-plugin 组合处理的速度快得多。
便利性
在 Webpack 中,您必须例如将 CSS 和 SVG 文件导入到 JavaScript 中才能处理它们,这有时可能非常棘手且令人困惑。使用 Gulp,您无需调整工作流程。
感谢作者提供的示例和描述。
将 Gulp 和 Webpack 组合在一起以解决彼此缺点,这是一件很棒的事情。前段时间,当我只使用 Gulp 时,我一直在寻找插件来将每个 JavaScript 文件连接在一起,但除了集成 Gulp + Browserify 或 Webpack 打包器之外,没有找到更好的解决方案。我之前在我的 React SPA 中使用过 Webpack,并且知道 Webpack 对 JS 来说有多强大。
这就是我决定采用这种方式的原因。
以下是我 gulp 3.9、browsersync 和 webpack 中间件栈的示例
https://github.com/wwwebman/gulp-webpack-starter
这并不完全正确。如果您不想在 JavaScript 文件中显式导入 (S)CSS,只需将您的 CSS 添加到入口点即可。您演示了多个文件构成 webpack 入口点,只需将
'style!css!./path/to/file.css'
添加到入口配置即可。如果您不想内联 CSS 并由 JavaScript 注入,webpack 也可以为您创建静态 css 文件。此外,此处用于热加载的 webpack 配置比必需的复杂得多。您无需为热加载添加入口文件,只需将
devServer.hot
设置为true
即可。再说一次,我不得不表示不同意。webpack 中的静态资源管理比使用 gulp 强大得多,尤其是在您理解加载器的情况下。例如,使用
html-loader
会自动对 HTML 中引用的静态文件(图像等)使用 webpack 加载器。如果您不想内联文件,只需使用file-loader
,然后您将获得 webpack 找不到资源时构建失败的额外好处,例如,您删除了一个资源并且可能忘记了引用。如果您不使用 webpack 管理您的静态资源,您将失去这一点。不要做扫兴的人,但将 Webpack、Gulp 和 Browsersync 组合在一起只会使一切都比需要复杂得多。我个人只会使用 webpack。如果您需要 BrowserSync 的更多功能(例如在浏览器之间同步用户事件,这是它最初的设计目的),那么 webpack 提供了一个 browsersync 插件。如果您更喜欢更传统的设置,只需使用 Gulp 和 BrowserSync 即可。尝试将所有内容组合在一起只会创建一个难以更新的脆弱设置。
嘿,Ken,我最近从 grunt 和 webpack 1 迁移到 NPM 命令和 webpack 2。我必须承认我感受到了一些 Pascal 的痛苦。似乎我的编译时间比以前更长了。此外,我们从 dist 文件夹中运行我们的开发站点,但这会导致我们更改包含资源字符串的 .json 文件时出现问题,因为该文件不会自动复制到 dist 文件夹。大多数项目是否通过从应用程序文件夹中提供服务来避免这种情况?或者我是否缺少另一个插件。像这样的概念在 grunt/gulp 中似乎更简单明了。
我每天都在使用 Webpack,我非常喜欢它,但它仍然比 Gulp 或 Grunt 复杂 100 倍,需要理解、设置和调整。
此外,那里的大多数 Webpack 样板代码都没有处理静态资源优化,例如图像优化、精灵图生成等。似乎由于某种原因,社区在 Grunt/Gulp 和 Webpack 之间的切换中失去了这一点。我用 Webpack 设置了它们,这确实很痛苦,所以我知道并行使用 Gulp 来处理这些问题为什么有用。
Chris 最近讨论了依赖项问题(请参阅此页面上的评论 https://css-tricks.org.cn/projects-need-react/),我认为有必要重复一下。
例如,为了开始使用 webpack,您需要承诺使用一系列技术。我认为问题不在于列表是什么(如果我说“node.js”,您很可能会回答您喜欢 node.js),问题在于该列表使人承诺使用一种完整的、精心设计的流程风格——编译资源、运行本地服务器、包管理器等。所有这些都感觉非常像以计算机科学为导向的开发思维方式:强调抽象、简化和工具。但这种思维方式已被证明不利于 Web,在 Web 中,事情需要实用、灵活且宽容。在我看来,HTML5 对这种过度工程化做出了反击,并回归了看待 Web 的更“蓝领”的方式:它是一个低效的、不断变化的、意大利面条式的、充满黑客的世界,但它都很简单——底层是文本文件和 URL。
与这种朝着增加依赖项和编译的趋势同时出现的,是试图使 WordPress 成为“栈”的一部分——像 apache、PHP、MySQL 等基础设施的一部分。例如,如果您查看此站点的代码,您会看到许多 JS 文件在各个地方加载,通常是动态编写的——其中许多文件乍一看似乎致力于以各种方式“优化”站点。我看到一个 SEO 模块,总是有大量的 Google 脚本、Twitter 脚本、FB 脚本等。
如果 Webpack 等工具旨在某种程度上简化交付或使资产组织更容易,那么似乎在某些地方存在某种脱节。我越来越频繁地看到 WP 网站加载数十个没人理解或跟踪的插件;我看到开发人员盲目地将“build”和“install”粘贴到他们的控制台中,安装越来越多的 alpha 或 beta 服务器插件,并在各处留下被遗弃的复杂框架,这些框架与他们难以理解的代码紧密绑定。废弃代码越来越多——而且不是 HTML5 试图通过允许混乱来解决的那种废弃代码。事情并没有变得更精简或更简单。中间件 Web 开发工具的激增是一团糟。
Gulp 4.0 现在安全使用了吗?它已经处于 beta 阶段很长时间了,以至于我在想所有开发人员是否都放弃了它,转而使用 WebPack。
是的,可以使用。
它已经成为我工作流程的一部分超过半年了。
在 BrowserSync 任务设置中,我认为您可能有一个错别字。代码片段说文件名是 webpack.js,但您有以下行
import { config as webpackConfig } from './webpack'
这意味着您还从名为 webpack.js 的本地文件导入命名导出。
BrowserSync 任务文件的名称是否与 webpack.js 不同?
糟糕,你说得对。谢谢!
BrowserSync 任务应该在
server.js
中,而不是webpack.js
中。我会联系管理员处理此事。