🎯 empfohlene Sammlungen
Balanced sample collections from various categories for you to explore
Parcel Bundling-Tool
Parcel Null-Konfigurations-Bundler-Beispiele einschließlich Projekt-Setup, Plugins und erweiterter Konfiguration
💻 Parcel Grundkonfiguration json
🟢 simple
⭐
Mit dem Parcel Null-Konfigurations-Bundler für moderne Web-Anwendungen beginnen
⏱️ 15 min
🏷️ parcel, setup, configuration
Prerequisites:
Node.js knowledge, JavaScript basics
// Parcel Basic Setup Examples
// Zero-configuration bundler for modern web applications
// ===== PROJECT STRUCTURE =====
/*
my-project/
├── src/
│ ├── index.html # Entry HTML file
│ ├── index.js # Main JavaScript entry
│ ├── styles/
│ │ └── main.css # CSS styles
│ ├── images/
│ │ └── logo.png # Image assets
│ └── components/
│ └── App.js # React component
├── public/ # Static assets
│ └── favicon.ico
├── package.json # Project configuration
├── .babelrc # Babel configuration
├── .parcelrc # Parcel configuration (optional)
└── tsconfig.json # TypeScript configuration (if needed)
*/
// ===== package.json =====
{
"name": "my-parcel-app",
"version": "1.0.0",
"description": "Modern web application built with Parcel",
"main": "src/index.js",
"scripts": {
"start": "parcel",
"build": "parcel build",
"dev": "parcel src/index.html",
"serve": "parcel serve src/index.html",
"clean": "rm -rf dist .cache",
"test": "jest",
"lint": "eslint src/**/*.{js,jsx,ts,tsx}",
"type-check": "tsc --noEmit"
},
"keywords": ["parcel", "bundler", "javascript", "css", "html"],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0",
"axios": "^1.3.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"parcel": "^2.8.0",
"@parcel/config-default": "^2.8.0",
"@parcel/core": "^2.8.0",
"@parcel/bundler-default": "^2.8.0",
"@parcel/optimizer-css": "^2.8.0",
"@parcel/optimizer-htmlnano": "^2.8.0",
"@parcel/packager-css": "^2.8.0",
"@parcel/packager-html": "^2.8.0",
"@parcel/packager-js": "^2.8.0",
"@parcel/packager-raw": "^2.8.0",
"@parcel/reporter-cli": "^2.8.0",
"@parcel/transformer-babel": "^2.8.0",
"@parcel/transformer-css": "^2.8.0",
"@parcel/transformer-html": "^2.8.0",
"@parcel/transformer-image": "^2.8.0",
"@parcel/transformer-js": "^2.8.0",
"@parcel/transformer-json": "^2.8.0",
"@parcel/transformer-postcss": "^2.8.0",
"@parcel/transformer-raw": "^2.8.0",
"@parcel/transformer-sass": "^2.8.0",
"@parcel/transformer-svg": "^2.8.0",
"@parcel/transformer-typescript-tsc": "^2.8.0",
"process": "^0.11.10",
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/preset-react": "^7.18.0",
"@babel/preset-typescript": "^7.18.0",
"typescript": "^4.9.0",
"eslint": "^8.34.0",
"eslint-config-react-app": "^7.0.1",
"prettier": "^2.8.0",
"sass": "^1.58.0",
"tailwindcss": "^3.2.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.0",
"jest": "^29.4.0",
"@testing-library/react": "^13.4.0",
"@testing-library/jest-dom": "^5.16.0"
},
"browserslist": [
"last 2 versions",
"not dead",
"not ie 11"
]
}
// ===== .parcelrc (Optional Configuration) =====
{
"extends": "@parcel/config-default",
"transformers": {
"*.{js,jsx,ts,tsx}": [
"@parcel/transformer-js",
"@parcel/transformer-babel"
],
"*.{css,scss,sass}": [
"@parcel/transformer-css",
"@parcel/transformer-postcss"
],
"*.{html}": [
"@parcel/transformer-html"
],
"*.{json}": [
"@parcel/transformer-json"
],
"*.{svg}": [
"@parcel/transformer-svg"
],
"*.{png,jpg,jpeg,gif,webp}": [
"@parcel/transformer-image"
]
},
"optimizers": {
"*.{css}": [
"@parcel/optimizer-css"
],
"*.{html}": [
"@parcel/optimizer-htmlnano"
]
},
"browsers": {
"production": [
"> 0.25%",
"not dead"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"reporters": [
"@parcel/reporter-cli"
]
}
// ===== .babelrc =====
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"last 2 versions",
"not dead",
"not ie 11"
]
},
"modules": false,
"useBuiltIns": "usage",
"corejs": 3
}
],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread"
]
}
// ===== tsconfig.json =====
{
"compilerOptions": {
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ES6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@assets/*": ["src/assets/*"]
}
},
"include": [
"src/**/*",
"public/**/*"
],
"exclude": [
"node_modules",
"dist",
".cache"
]
}
// ===== src/index.html =====
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>My Parcel App</title>
<link rel="shortcut icon" href="/favicon.ico">
<meta name="description" content="Modern web application built with Parcel">
</head>
<body>
<div id="root"></div>
<!-- Parcel will automatically inject scripts here -->
</body>
</html>
// ===== src/index.js =====
import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import './styles/main.css';
import App from './components/App';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// Hot Module Replacement support
if (module.hot) {
module.hot.accept('./components/App', () => {
const NextApp = require('./components/App').default;
root.render(
<React.StrictMode>
<BrowserRouter>
<NextApp />
</BrowserRouter>
</React.StrictMode>
);
});
}
// ===== src/components/App.js =====
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import Home from './Home';
import About from './About';
import Contact from './Contact';
import Navigation from './Navigation';
const App = () => {
return (
<div className="app">
<Navigation />
<main className="main-content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</main>
<footer className="footer">
<p>© 2024 My Parcel App. All rights reserved.</p>
</footer>
</div>
);
};
export default App;
// ===== src/styles/main.css =====
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
/* Custom styles */
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
.footer {
background: #333;
color: white;
text-align: center;
padding: 1rem;
margin-top: auto;
}
/* CSS Modules support */
.container {
@apply max-w-6xl mx-auto px-4;
}
.btn {
@apply bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded transition-colors duration-200;
}
.card {
@apply bg-white rounded-lg shadow-md p-6 mb-4;
}
// ===== tailwind.config.js =====
module.exports = {
content: [
"./src/**/*.{html,js,jsx,ts,tsx}",
"./public/**/*.html"
],
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8'
}
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif']
}
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography')
]
}
// ===== postcss.config.js =====
module.exports = {
plugins: [
'tailwindcss',
'autoprefixer'
]
}
// ===== .env =====
# Environment variables
API_URL=https://api.example.com
APP_NAME=My Parcel App
APP_VERSION=1.0.0
NODE_ENV=development
// ===== .env.production =====
API_URL=https://api.prod.example.com
APP_NAME=My Parcel App
APP_VERSION=1.0.0
NODE_ENV=production
// ===== jest.config.js =====
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
moduleNameMapping: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/fileMock.js'
},
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/index.js',
'!src/reportWebVitals.js'
]
};
// ===== .eslintrc.json =====
{
"extends": [
"react-app",
"react-app/jest"
],
"rules": {
"no-console": "warn",
"no-unused-vars": "error",
"prefer-const": "error"
},
"overrides": [
{
"files": ["**/*.test.js", "**/*.test.jsx", "**/*.test.ts", "**/*.test.tsx"],
"env": {
"jest": true
}
}
]
}
// ===== .prettierrc =====
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false
}
💻 Parcel Plugins und Konfiguration javascript
🟡 intermediate
⭐⭐⭐⭐
Erweiterte Parcel-Konfiguration mit Plugins, benutzerdefinierten Transformern und Build-Optimierung
⏱️ 35 min
🏷️ parcel, plugins, configuration
Prerequisites:
Parcel basics, JavaScript ES6+, Plugin development
// Advanced Parcel Configuration and Custom Plugins
// Extending Parcel with custom transformers, plugins, and optimization
// ===== CUSTOM TRANSFORMER PLUGIN =====
// parcel-transformer-custom.js
const { Transformer } = require('@parcel/core');
class CustomTransformer extends Transformer {
constructor(options = {}) {
super(options);
this.customOptions = options;
}
// Check if this transformer can handle the asset
async canTransform(asset) {
const extension = asset.filePath.split('.').pop().toLowerCase();
return ['custom', 'xyz'].includes(extension);
}
// Transform the asset
async transform(asset) {
const source = await asset.getCode();
const filePath = asset.filePath;
// Custom transformation logic
let transformedCode = source;
// Example: Add timestamp
const timestamp = new Date().toISOString();
transformedCode = `
// Generated from ${filePath} at ${timestamp}
${transformedCode}
export const metadata = {
sourceFile: '${filePath}',
generatedAt: '${timestamp}',
transformer: 'custom'
};
`;
return {
content: transformedCode,
map: null // No source map for this example
};
}
}
module.exports = (options) => {
return new CustomTransformer(options);
};
// ===== CUSTOM BUNDLER PLUGIN =====
// parcel-plugin-analyzer.js
const { Bundler } = require('@parcel/core');
const fs = require('fs');
const path = require('path');
class AnalyzerBundler extends Bundler {
constructor(...args) {
super(...args);
this.bundleStats = new Map();
}
async bundle() {
const startTime = Date.now();
// Run the original bundling process
const bundleGraph = await super.bundle();
const endTime = Date.now();
const buildTime = endTime - startTime;
// Analyze bundle
await this.analyzeBundle(bundleGraph, buildTime);
return bundleGraph;
}
async analyzeBundle(bundleGraph, buildTime) {
const stats = {
buildTime,
bundles: [],
assets: [],
totalSize: 0,
dependencies: new Set()
};
// Analyze bundles
bundleGraph.getBundles().forEach(bundle => {
const bundleInfo = {
id: bundle.id,
filePath: bundle.filePath,
type: bundle.type,
size: 0,
assets: []
};
bundle.getEntryAssets().forEach(asset => {
const assetSize = asset.stats.size;
bundleInfo.size += assetSize;
bundleInfo.assets.push({
id: asset.id,
filePath: asset.filePath,
type: asset.type,
size: assetSize
});
stats.assets.push({
filePath: asset.filePath,
size: assetSize,
type: asset.type
});
});
stats.bundles.push(bundleInfo);
stats.totalSize += bundleInfo.size;
});
// Collect dependencies
bundleGraph.getDependencyGraph().getNodes().forEach(node => {
if (node.value.type === 'dependency') {
stats.dependencies.add(node.value.specifier);
}
});
// Write analysis report
await this.writeAnalysisReport(stats);
}
async writeAnalysisReport(stats) {
const report = {
timestamp: new Date().toISOString(),
buildTime: stats.buildTime,
bundles: stats.bundles,
totalSize: stats.totalSize,
dependencyCount: stats.dependencies.size,
dependencies: Array.from(stats.dependencies)
};
// Write JSON report
const reportPath = path.join(this.options.outDir, 'bundle-analysis.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
// Write HTML report
const htmlReport = this.generateHTMLReport(report);
const htmlPath = path.join(this.options.outDir, 'bundle-analysis.html');
fs.writeFileSync(htmlPath, htmlReport);
console.log(`📊 Bundle analysis written to ${reportPath}`);
console.log(`🌐 HTML report available at ${htmlPath}`);
}
generateHTMLReport(report) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bundle Analysis Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { text-align: center; margin-bottom: 30px; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px; }
.stat-card { background: #f8f9fa; padding: 20px; border-radius: 8px; text-align: center; }
.stat-number { font-size: 2em; font-weight: bold; color: #007bff; }
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background: #f8f9fa; font-weight: bold; }
.chart { height: 300px; background: #f8f9fa; border-radius: 8px; margin: 20px 0; display: flex; align-items: center; justify-content: center; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Bundle Analysis Report</h1>
<p>Generated on ${report.timestamp}</p>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-number">${report.bundles.length}</div>
<div>Bundles</div>
</div>
<div class="stat-card">
<div class="stat-number">${(report.totalSize / 1024).toFixed(2)} KB</div>
<div>Total Size</div>
</div>
<div class="stat-card">
<div class="stat-number">${report.buildTime} ms</div>
<div>Build Time</div>
</div>
<div class="stat-card">
<div class="stat-number">${report.dependencyCount}</div>
<div>Dependencies</div>
</div>
</div>
<h2>Bundle Details</h2>
<table>
<thead>
<tr>
<th>Bundle</th>
<th>Type</th>
<th>Size</th>
<th>Assets</th>
</tr>
</thead>
<tbody>
${report.bundles.map(bundle => `
<tr>
<td>${bundle.filePath || bundle.id}</td>
<td>${bundle.type}</td>
<td>${(bundle.size / 1024).toFixed(2)} KB</td>
<td>${bundle.assets.length}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</body>
</html>
`;
}
}
module.exports = (options) => {
return new AnalyzerBundler(options);
};
// ===== ADVANCED .parcelrc CONFIGURATION =====
{
"extends": "@parcel/config-default",
"bundler": "./parcel-plugin-analyzer.js",
"transformers": {
"*.{js,jsx,ts,tsx}": [
"@parcel/transformer-js",
"@parcel/transformer-babel"
],
"*.{css,scss,sass,less}": [
"@parcel/transformer-css",
"@parcel/transformer-postcss"
],
"*.{html,hbs}": [
"@parcel/transformer-html"
],
"*.{json}": [
"@parcel/transformer-json"
],
"*.{svg}": [
"@parcel/transformer-svg"
],
"*.{png,jpg,jpeg,gif,webp,avif}": [
"@parcel/transformer-image"
],
"*.{txt,md}": [
"./parcel-transformer-markdown.js"
],
"*.{custom,xyz}": [
"./parcel-transformer-custom.js"
]
},
"optimizers": {
"*.{css,scss,sass}": [
"@parcel/optimizer-css",
"./parcel-optimizer-critical-css.js"
],
"*.{html}": [
"@parcel/optimizer-htmlnano",
"./parcel-optimizer-minify-html.js"
],
"*.{js,jsx,ts,tsx}": [
"@parcel/optimizer-terser"
],
"*.{json}": [
"./parcel-optimizer-json.js"
]
},
"namers": {
"js": "[name].[hash:8].js",
"css": "[name].[hash:8].css",
"html": "[name].[hash:8].html",
"image": "[name].[hash:8].[ext]",
"*": "[name].[hash:8].[ext]"
},
"compressors": {
"*.{png,jpg,jpeg,gif,webp}": [
"@parcel/compressor-sharp"
],
"*.{svg}": [
"@parcel/compressor-svgo"
]
},
"packagers": {
"*.{js,jsx,ts,tsx}": [
"@parcel/packager-js"
],
"*.{css,scss,sass,less}": [
"@parcel/packager-css"
],
"*.{html,hbs}": [
"@parcel/packager-html"
],
"*.{json}": [
"@parcel/packager-json"
],
"*.{png,jpg,jpeg,gif,webp,svg}": [
"@parcel/packager-raw"
]
},
"reporters": [
"@parcel/reporter-cli",
"@parcel/reporter-dev-server",
"./parcel-reporter-metrics.js"
],
"resolvers": [
"@parcel/resolver-default",
"./parcel-resolver-custom.js"
],
"runtimes": {
"library": "@parcel/runtime-js",
"service-worker": "@parcel/runtime-service-worker"
},
"browsers": {
"production": [
"> 0.2%",
"not dead",
"not op_mini all",
"not ie 11"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"serve": {
"port": 1234,
"host": "localhost",
"https": false,
"hmr": true,
"hmrPort": 1235
},
"build": {
"target": "browser",
"scopeHoist": false,
"distDir": "dist",
"cache": true,
"cacheDir": ".cache",
"publicUrl": "/",
"sourceMap": true
}
}
// ===== CUSTOM OPTIMIZER PLUGIN =====
// parcel-optimizer-critical-css.js
const { Optimizer } = require('@parcel/core');
const postcss = require('postcss');
const cssCritical = require('postcss-critical');
class CriticalCSSOptimizer extends Optimizer {
async optimize({ bundle, bundleGraph, options }) {
// Only optimize CSS bundles
if (bundle.type !== 'css') {
return { contents: await bundle.getCode() };
}
const source = await bundle.getCode();
const criticalCSS = await this.extractCriticalCSS(source, bundleGraph);
return {
contents: criticalCSS,
map: null
};
}
async extractCriticalCSS(css, bundleGraph) {
// Get HTML entries to know what's critical
const htmlBundles = bundleGraph.getBundles().filter(b => b.type === 'html');
if (htmlBundles.length === 0) {
return css; // No HTML to extract critical CSS from
}
try {
const result = await postcss([
cssCritical({
extract: true,
inline: false,
outputPath: 'critical.css'
})
]).process(css, { from: undefined });
return result.css;
} catch (error) {
console.warn('Failed to extract critical CSS:', error);
return css;
}
}
}
module.exports = (options) => {
return new CriticalCSSOptimizer(options);
};
// ===== PARCEL API USAGE =====
// build-script.js
import { Parcel } from '@parcel/core';
import { WorkerFarm } from '@parcel/workers';
import { defaultConfig } from '@parcel/config-default';
import { fork } from 'child_process';
import path from 'path';
async function buildProject() {
const parcel = new Parcel({
entries: ['src/index.html'],
config: defaultConfig,
defaultConfig: {},
patchConsole: true,
logLevel: 'info',
shouldPatchConsole: true,
shouldAutoInstall: true,
shouldUseWorkerThreads: true,
shouldProgressReporting: true,
cacheDir: '.cache',
mode: 'production',
env: {
NODE_ENV: 'production',
PUBLIC_URL: '/app'
},
production: true,
serveOptions: {
host: 'localhost',
port: 1234
},
shouldDisableCache: false,
shouldContentHash: true
});
try {
await parcel.run();
console.log('Build completed successfully!');
// Get build metrics
const bundleGraph = parcel.getBundleGraph();
const bundles = bundleGraph.getBundles();
console.log('\nBuild Summary:');
bundles.forEach(bundle => {
console.log(`- ${bundle.filePath} (${bundle.type})`);
});
} catch (error) {
console.error('Build failed:', error);
process.exit(1);
} finally {
await parcel.stop();
}
}
// Run the build
buildProject();
// ===== CUSTOM REPORTER PLUGIN =====
// parcel-reporter-metrics.js
const { Reporter } = require('@parcel/core');
const fs = require('fs');
const path = require('path');
class MetricsReporter extends Reporter {
constructor(options) {
super(options);
this.metrics = {
buildStartTime: null,
buildEndTime: null,
bundles: [],
assets: [],
cacheHits: 0,
cacheMisses: 0,
errors: [],
warnings: []
};
}
async report({ bundleGraph, buildTime, options }) {
this.metrics.buildStartTime = Date.now() - buildTime;
this.metrics.buildEndTime = Date.now();
// Collect bundle metrics
bundleGraph.getBundles().forEach(bundle => {
this.metrics.bundles.push({
id: bundle.id,
filePath: bundle.filePath,
type: bundle.type,
size: this.getBundleSize(bundle)
});
bundle.getEntryAssets().forEach(asset => {
this.metrics.assets.push({
id: asset.id,
filePath: asset.filePath,
type: asset.type,
size: asset.stats.size
});
});
});
// Write metrics to file
await this.writeMetrics();
// Display metrics
this.displayMetrics();
}
getBundleSize(bundle) {
let size = 0;
bundle.getEntryAssets().forEach(asset => {
size += asset.stats.size;
});
return size;
}
async writeMetrics() {
const metricsPath = path.join(this.options.outDir, 'build-metrics.json');
const metrics = {
...this.metrics,
buildDuration: this.metrics.buildEndTime - this.metrics.buildStartTime,
totalBundleSize: this.metrics.bundles.reduce((sum, b) => sum + b.size, 0),
totalAssetSize: this.metrics.assets.reduce((sum, a) => sum + a.size, 0),
timestamp: new Date().toISOString()
};
fs.writeFileSync(metricsPath, JSON.stringify(metrics, null, 2));
}
displayMetrics() {
console.log('\n📊 Build Metrics Report');
console.log('========================');
console.log(`Build Time: ${this.metrics.buildEndTime - this.metrics.buildStartTime}ms`);
console.log(`Bundles: ${this.metrics.bundles.length}`);
console.log(`Assets: ${this.metrics.assets.length}`);
console.log(`Total Size: ${(this.metrics.bundles.reduce((sum, b) => sum + b.size, 0) / 1024).toFixed(2)}KB`);
console.log('\nBundle Details:');
this.metrics.bundles.forEach(bundle => {
console.log(` ${bundle.filePath}: ${(bundle.size / 1024).toFixed(2)}KB`);
});
}
}
module.exports = (options) => {
return new MetricsReporter(options);
};
// ===== WORKFLOW INTEGRATION =====
// .github/workflows/build.yml
name: Build with Parcel
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build with Parcel
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: dist-${{ matrix.node-version }}
path: dist/
- name: Deploy to staging (if main branch)
if: github.ref == 'refs/heads/main'
run: |
echo "Deploy to staging environment"
# Add deployment commands here
// ===== VERCEL CONFIGURATION =====
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"installCommand": "npm ci",
"framework": null,
"devCommand": "npm start",
"functions": {},
"headers": [
{
"source": "/static/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
],
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
// ===== NETLIFY CONFIGURATION =====
// netlify.toml
[build]
publish = "dist"
command = "npm run build"
[build.environment]
NODE_VERSION = "18"
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
💻 Parcel Workflow und Deployment yaml
🟡 intermediate
⭐⭐⭐
Vollständiger Parcel-Entwicklungs-Workflow einschließlich Tests, CI/CD und Deployment-Strategien
⏱️ 30 min
🏷️ parcel, workflow, deployment
Prerequisites:
Parcel basics, DevOps concepts, CI/CD knowledge
# Parcel Development Workflow and Deployment
# Complete setup for modern web application development with Parcel
# ===== DOCKER SETUP =====
# Dockerfile for Node.js application with Parcel
FROM node:18-alpine AS base
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
# Development stage
FROM base AS development
RUN npm ci
COPY . .
EXPOSE 1234
CMD ["npm", "start"]
# Production build stage
FROM base AS build
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine AS production
# Copy build artifacts
COPY --from=build /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# ===== DOCKER COMPOSE =====
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
target: development
ports:
- "1234:1234"
- "1235:1235" # HMR port
volumes:
- .:/app
- /app/node_modules
- /app/.cache
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
command: npm start
build:
build:
context: .
target: build
volumes:
- ./dist:/app/dist
environment:
- NODE_ENV=production
command: npm run build
production:
build:
context: .
target: production
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- build
# Testing service
test:
build:
context: .
target: development
volumes:
- .:/app
- /app/node_modules
- /app/.cache
environment:
- NODE_ENV=test
- CI=true
command: npm test
# ===== Nginx Configuration =====
# nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;" always;
# Handle client-side routing
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}
# ===== GITHUB ACTIONS WORKFLOW =====
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
NODE_VERSION: 18
CACHE_VERSION: v1
jobs:
# Lint and type checking
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run TypeScript check
run: npm run type-check
# Test suite
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test:ci
- name: Upload coverage reports
uses: codecov/codecov-action@v3
if: success()
with:
directory: ./coverage
# Build application
build:
runs-on: ubuntu-latest
needs: [lint, test]
strategy:
matrix:
environment: [development, production]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build:${{ matrix.environment }}
env:
NODE_ENV: ${{ matrix.environment }}
- name: Analyze bundle
run: npm run analyze
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-${{ matrix.environment }}
path: |
dist/
bundle-analysis.html
retention-days: 7
# Security scanning
security:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run security audit
run: npm audit --audit-level=high
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# Docker build and push
docker:
runs-on: ubuntu-latest
needs: [build]
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/my-parcel-app:latest
${{ secrets.DOCKER_USERNAME }}/my-parcel-app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Deploy to staging
deploy-staging:
runs-on: ubuntu-latest
needs: [build, docker]
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- name: Deploy to staging
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USERNAME }}
key: ${{ secrets.STAGING_SSH_KEY }}
script: |
cd /opt/my-parcel-app
docker-compose pull
docker-compose up -d
docker-compose exec app npm run migrate
- name: Run integration tests
run: npm run test:e2e
# Deploy to production
deploy-production:
runs-on: ubuntu-latest
needs: [build, docker]
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to production
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USERNAME }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
cd /opt/my-parcel-app
docker-compose pull
docker-compose up -d
docker-compose exec app npm run migrate
# ===== GITLAB CI/CD =====
# .gitlab-ci.yml
stages:
- lint
- test
- build
- deploy
variables:
NODE_VERSION: "18"
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/
before_script:
- npm ci
# Linting
lint:
stage: lint
script:
- npm run lint
- npm run type-check
only:
- branches
- merge_requests
# Testing
test:
stage: test
script:
- npm run test:ci
- npm run test:e2e
coverage: '/All files[^|]*\|[^|]*\s+\d+.\d+\s*/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
paths:
- coverage/
only:
- branches
- merge_requests
# Building
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
- bundle-analysis.html
expire_in: 1 week
only:
- branches
# Deploy to staging
deploy_staging:
stage: deploy
script:
- echo "Deploy to staging environment"
environment:
name: staging
url: https://staging.myapp.com
only:
- develop
# Deploy to production
deploy_production:
stage: deploy
script:
- echo "Deploy to production environment"
environment:
name: production
url: https://myapp.com
when: manual
only:
- main
# ===== TRAVIS CI =====
# .travis.yml
language: node_js
node_js:
- "18"
cache:
npm: true
directories:
- .cache
branches:
only:
- main
- develop
- /^release\/.*$/
install:
- npm ci
script:
- npm run lint
- npm run type-check
- npm run test:ci
- npm run build
after_success:
- npm run coverage
deploy:
provider: script
script:
- echo "Deploying to staging"
on:
branch: develop
# ===== VAGRANT SETUP =====
# Vagrantfile
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/focal64"
# Forward ports
config.vm.network "forwarded_port", guest: 1234, host: 1234
config.vm.network "forwarded_port", guest: 1235, host: 1235
# Sync folders
config.vm.synced_folder ".", "/vagrant",
mount_options: ["dmode=777,fmode=777"],
exclude: [".git/", "node_modules/", ".cache/"]
# Provision the VM
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y curl git
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt-get install -y nodejs
# Install PM2 for process management
npm install -g pm2
# Install project dependencies
cd /vagrant
npm ci
SHELL
# Set up PM2 process
config.vm.provision "shell", run: "always", inline: <<-SHELL
cd /vagrant
pm2 delete all 2>/dev/null || true
pm2 start ecosystem.config.js --env development
SHELL
end
# PM2 ecosystem configuration
module.exports = {
apps: [{
name: 'my-parcel-app',
script: 'npm',
args: 'start',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 1234
},
env_production: {
NODE_ENV: 'production',
PORT: 1234
},
log_date_format: 'YYYY-MM-DD HH:mm Z',
error_file: 'logs/err.log',
out_file: 'logs/out.log',
log_file: 'logs/combined.log',
time: true
}]
};
# ===== KUBERNETES DEPLOYMENT =====
# k8s/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-parcel-app
labels:
app: my-parcel-app
spec:
replicas: 3
selector:
matchLabels:
app: my-parcel-app
template:
metadata:
labels:
app: my-parcel-app
spec:
containers:
- name: my-parcel-app
image: myusername/my-parcel-app:latest
ports:
- containerPort: 80
env:
- name: NODE_ENV
value: "production"
- name: API_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: API_URL
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: my-parcel-app-service
spec:
selector:
app: my-parcel-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-parcel-app-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
tls:
- hosts:
- myapp.com
secretName: myapp-tls
rules:
- host: myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-parcel-app-service
port:
number: 80
# ===== TERRAFORM INFRASTRUCTURE =====
# terraform/main.tf
provider "aws" {
region = var.aws_region
}
# S3 bucket for static assets
resource "aws_s3_bucket" "static_assets" {
bucket = "${var.project_name}-static-assets-${random_id.bucket_suffix.result}"
acl = "private"
website {
index_document = "index.html"
error_document = "404.html"
}
tags = {
Name = var.project_name
Environment = var.environment
}
}
# CloudFront distribution
resource "aws_cloudfront_distribution" "cdn" {
origin {
domain_name = aws_s3_bucket.static_assets.bucket_regional_domain_name
origin_id = "S3-${aws_s3_bucket.static_assets.bucket}"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.default.id
}
}
enabled = true
is_ipv6_enabled = true
comment = "${var.project_name} CDN"
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${aws_s3_bucket.static_assets.bucket}"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
compress = true
viewer_protocol_policy = "redirect-to-https"
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
tags = {
Name = var.project_name
Environment = var.environment
}
}
# Route 53 hosted zone
resource "aws_route53_record" "www" {
zone_id = data.aws_route53_zone.main.zone_id
name = "www.${var.domain_name}"
type = "A"
alias {
name = aws_cloudfront_distribution.cdn.domain_name
zone_id = aws_cloudfront_distribution.cdn.hosted_zone_id
evaluate_target_health = true
}
}