Параметр target в конфигурации Webpack определяет среду
выполнения итогового бандла. От выбранного значения зависит:
window, document,
global;Webpack умеет собирать код не только для браузера, но и для серверных приложений, Electron, worker-потоков и гибридных платформ.
Базовый пример:
module.exports = {
target: 'web'
}
Поддерживаются разные значения:
target: 'web'
target: 'node'
target: 'electron-main'
target: 'electron-renderer'
target: 'webworker'
target: 'web'web — стандартная цель Webpack. Используется по
умолчанию.
module.exports = {
target: 'web'
}
В этом режиме Webpack предполагает:
window;<script>;Webpack генерирует runtime, адаптированный под браузер.
Например:
import('./module.js')
превращается в загрузку отдельного JS-файла через script tag.
Runtime примерно выполняет:
const script = document.createElement('script')
script.src = chunkUrl
document.head.appendChild(script)
Для Node.js такой механизм неприменим.
При target: 'web' доступны:
document.querySelector()
window.location
localStorage
navigator.userAgent
Пример:
const button = document.querySelector('.btn')
button.addEventListener('click', () => {
console.log(window.location.href)
})
Код Node.js API работать не будет:
const fs = require('fs')
Ошибка:
Module not found: Error: Can't resolve 'fs'
Webpack 5 перестал автоматически полифилить Node.js-модули.
Ранее автоматически подключались:
path-browserifycrypto-browserifystream-browserifyТеперь это необходимо делать вручную.
Пример:
npm install path-browserify
Конфигурация:
const path = require('path')
module.exports = {
target: 'web',
resolve: {
fallback: {
path: require.resolve('path-browserify')
}
}
}
Иногда Node API не нужен вовсе.
Тогда можно явно отключить модуль:
module.exports = {
resolve: {
fallback: {
fs: false
}
}
}
target: 'node'Webpack может собирать серверные приложения.
module.exports = {
target: 'node'
}
В этом режиме:
require;window не существует.При динамическом импорте:
import('./service.js')
Webpack использует:
require('./service.js')
а не <script>.
Доступны:
const fs = require('fs')
const path = require('path')
const os = require('os')
Пример:
const fs = require('fs')
const content = fs.readFileSync('./data.txt', 'utf-8')
console.log(content)
Следующий код вызовет ошибку:
document.querySelector('.app')
Ошибка:
ReferenceError: document is not defined
В Node.js существует:
global
В браузере:
window
Webpack учитывает это различие автоматически.
Часто используется:
module.exports = {
target: 'node',
output: {
filename: 'server.js'
}
}
Серверные сборки обычно не включают зависимости из
node_modules.
Используются externals.
Пример:
npm install webpack-node-externals
const nodeExternals = require('webpack-node-externals')
module.exports = {
target: 'node',
externals: [nodeExternals()]
}
Без externals:
bundle.js
содержит:
Размер может достигать десятков мегабайт.
С externals:
require('express')
остаётся внешним вызовом.
const path = require('path')
module.exports = {
mode: 'production',
target: 'node',
entry: './src/server.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'server.js'
}
}
__dirnameПри browser target значение может изменяться.
Для Node.js часто включают:
module.exports = {
target: 'node',
node: {
__dirname: false,
__filename: false
}
}
Electron сочетает:
У Electron несколько процессов:
Для каждого требуется собственный target.
Main Process — это backend Electron-приложения.
Используется:
target: 'electron-main'
Пример:
module.exports = {
target: 'electron-main'
}
Доступны:
Недоступны:
const { app, BrowserWindow } = require('electron')
function createWindow() {
const window = new BrowserWindow({
width: 1200,
height: 800
})
window.loadURL('http://localhost:3000')
}
app.whenReady().then(createWindow)
const path = require('path')
module.exports = {
target: 'electron-main',
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
}
}
Renderer — это браузерное окно Electron.
Используется:
target: 'electron-renderer'
Доступны одновременно:
Пример:
document.querySelector('.btn')
и одновременно:
const os = require('os')
const os = require('os')
document.querySelector('#info').innerHTML = `
Platform: ${os.platform()}
`
const path = require('path')
module.exports = {
target: 'electron-renderer',
entry: './src/renderer.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'renderer.js'
}
}
Preload Script выполняется между:
Обычно используется:
target: 'electron-preload'
Preload безопасно предоставляет API:
contextBridge.exposeInMainWorld()
Пример:
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('api', {
version: '1.0'
})
module.exports = {
target: 'electron-preload'
}
Крупные проекты часто имеют:
Webpack позволяет экспортировать массив конфигураций.
const path = require('path')
module.exports = [
{
target: 'web',
entry: './src/client.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'client.js'
}
},
{
target: 'node',
entry: './src/server.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'server.js'
}
}
]
Типичная архитектура:
src/
client/
server/
Конфигурации:
webpack.client.js
webpack.server.js
Часто используется merge.
npm install webpack-merge
const { merge } = require('webpack-merge')
const common = {
mode: 'production'
}
const client = merge(common, {
target: 'web'
})
const server = merge(common, {
target: 'node'
})
module.exports = [client, server]
target Webpack не заменяет targets
Babel.
Webpack определяет:
Babel определяет:
Пример:
presets: [
[
'@babel/preset-env',
{
targets: {
node: '18'
}
}
]
]
module.exports = {
target: 'node',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
}
]
}
}
Для worker-потоков используется:
target: 'webworker'
В worker отсутствуют:
Доступны:
self
postMessage()
onmessage
self.onmess age = event => {
const result = event.data * 2
self.postMessage(result)
}
module.exports = {
target: 'webworker'
}
Один и тот же код может выполняться:
Тогда target разделяется.
client bundle -> browser
server bundle -> node.js
const isBrowser = typeof window !== 'undefined'
if (typeof window !== 'undefined') {
localStorage.setItem('theme', 'dark')
}
<script src="chunk.js">
require('./chunk.js')
importScripts()
Webpack автоматически выбирает стратегию.
Это разные параметры.
target:
target: 'node'
определяет среду выполнения.
libraryTarget:
output: {
libraryTarget: 'commonjs2'
}
определяет формат экспорта библиотеки.
Иногда библиотека публикуется сразу в нескольких вариантах:
module.exports = [
{
target: 'web',
output: {
filename: 'library.browser.js'
}
},
{
target: 'node',
output: {
filename: 'library.node.js'
}
}
]
window.location
Ошибка:
window is not defined
require('fs')
Ошибка:
Can't resolve 'fs'
Опасная конфигурация:
nodeIntegration: true
Renderer получает полный доступ к Node.js.
Современный Electron рекомендует:
contextIsolation: true
sandbox: true
Webpack предоставляет:
compiler.options.target
Это полезно для кастомных плагинов.
Пример:
class TargetPlugin {
apply(compiler) {
console.log(compiler.options.target)
}
}
module.exports = (env) => {
const isServer = env.server
return {
target: isServer ? 'node' : 'web'
}
}
project/
src/
client/
server/
electron/
main/
renderer/
preload/
webpack/
webpack.client.js
webpack.server.js
webpack.electron.js
const path = require('path')
module.exports = [
{
mode: 'production',
target: 'web',
entry: './src/client/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'client.bundle.js'
}
},
{
mode: 'production',
target: 'node',
entry: './src/server/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'server.bundle.js'
}
},
{
mode: 'production',
target: 'electron-main',
entry: './src/electron/main/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'electron-main.js'
}
}
]