u
This commit is contained in:
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# ESLint cache
|
||||
.eslintcache
|
||||
|
||||
# Webpack bundle analyzer
|
||||
/bundlestats.html
|
||||
70
README.md
Normal file
70
README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# 3D Cube Viewer
|
||||
|
||||
A Three.js-based 3D cube viewer with webpack build system.
|
||||
|
||||
## Features
|
||||
|
||||
- Interactive 3D model viewer
|
||||
- OBJ/MTL file support
|
||||
- Texture loading
|
||||
- Glass material with transparency and reflections
|
||||
- Responsive design
|
||||
- Modern ES6+ JavaScript with webpack bundling
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Start the development server with hot module replacement:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The development server will start at `http://localhost:8080` and automatically open in your browser.
|
||||
|
||||
## Production Build
|
||||
|
||||
Build the project for production:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
This will create optimized, minified files in the `dist/` directory.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
threedee/
|
||||
├── src/
|
||||
│ ├── index.html # HTML template
|
||||
│ ├── index.js # Main JavaScript entry point
|
||||
│ └── styles.css # CSS styles
|
||||
├── cube.obj # 3D model file
|
||||
├── cube.mtl # Material definitions
|
||||
├── cube/ # Texture files
|
||||
│ ├── Carpet_01_1K.png
|
||||
│ ├── Denim_03_1K.png
|
||||
│ ├── Marble_01_1K.png
|
||||
│ └── Wood_Veneer_15_1K.png
|
||||
├── dist/ # Production build output (generated)
|
||||
├── webpack.config.js # Webpack configuration
|
||||
└── package.json # Project dependencies and scripts
|
||||
```
|
||||
|
||||
## Controls
|
||||
|
||||
- **Left click + drag**: Rotate around model
|
||||
- **Right click + drag**: Pan
|
||||
- **Mouse wheel**: Zoom in/out
|
||||
|
||||
## Technologies Used
|
||||
|
||||
- Three.js for 3D rendering
|
||||
- Webpack for bundling and development server
|
||||
- Modern ES6+ JavaScript modules
|
||||
4734
package-lock.json
generated
Normal file
4734
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
package.json
Normal file
26
package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "threedee-cube-viewer",
|
||||
"version": "1.0.0",
|
||||
"description": "3D Cube Viewer with Three.js",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "webpack serve --mode development",
|
||||
"build": "webpack --mode production",
|
||||
"build:dev": "webpack --mode development"
|
||||
},
|
||||
"dependencies": {
|
||||
"three": "^0.158.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"style-loader": "^3.3.3"
|
||||
},
|
||||
"keywords": ["three.js", "3d", "webgl"],
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
23
src/index.html
Normal file
23
src/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>3D Cube Viewer</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
Loading 3D Model...
|
||||
</div>
|
||||
|
||||
<div id="info">
|
||||
<h2>3D Cube Viewer</h2>
|
||||
<p>🖱️ Left click + drag: Rotate around model</p>
|
||||
<p>🖱️ Right click + drag: Pan</p>
|
||||
<p>⚙️ Mouse wheel: Zoom in/out</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
414
src/index.js
Normal file
414
src/index.js
Normal file
@@ -0,0 +1,414 @@
|
||||
import * as THREE from 'three';
|
||||
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
||||
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js';
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
||||
import './styles.css';
|
||||
|
||||
// Global variables
|
||||
let scene, camera, renderer, controls;
|
||||
let cube;
|
||||
|
||||
// Initialize the 3D scene
|
||||
function init() {
|
||||
// Create container
|
||||
const container = document.getElementById('container');
|
||||
|
||||
// Create scene
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x2c3e50);
|
||||
|
||||
// Create camera
|
||||
camera = new THREE.PerspectiveCamera(
|
||||
75, // Field of view
|
||||
window.innerWidth / window.innerHeight, // Aspect ratio
|
||||
0.1, // Near clipping plane
|
||||
1000 // Far clipping plane
|
||||
);
|
||||
camera.position.set(20, 20, 20);
|
||||
camera.lookAt(0, 0, 0);
|
||||
|
||||
// Create renderer
|
||||
renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: true
|
||||
});
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.shadowMap.enabled = true;
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
renderer.toneMappingExposure = 1.5;
|
||||
|
||||
// Enable proper transparency rendering
|
||||
renderer.sortObjects = true;
|
||||
renderer.autoClear = false;
|
||||
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
// Add orbit controls
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.05;
|
||||
controls.enableZoom = true;
|
||||
controls.enablePan = true;
|
||||
controls.enableRotate = true;
|
||||
|
||||
// Add lighting
|
||||
setupLighting();
|
||||
|
||||
// Load the cube model
|
||||
loadCubeModel();
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', onWindowResize, false);
|
||||
|
||||
// Start render loop
|
||||
animate();
|
||||
}
|
||||
|
||||
function setupLighting() {
|
||||
// Ambient light for overall illumination - increased intensity
|
||||
const ambientLight = new THREE.AmbientLight(0x404040, 1.2);
|
||||
scene.add(ambientLight);
|
||||
|
||||
// Directional light (like sunlight) - increased intensity
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
|
||||
directionalLight.position.set(10, 10, 5);
|
||||
directionalLight.castShadow = true;
|
||||
directionalLight.shadow.mapSize.width = 2048;
|
||||
directionalLight.shadow.mapSize.height = 2048;
|
||||
scene.add(directionalLight);
|
||||
|
||||
// Point light for additional illumination - increased intensity
|
||||
const pointLight = new THREE.PointLight(0xffffff, 0.8, 100);
|
||||
pointLight.position.set(-10, 10, 10);
|
||||
scene.add(pointLight);
|
||||
|
||||
// Interior point lights (will be repositioned when model loads) - increased intensity
|
||||
window.interiorLight = new THREE.PointLight(0xffffff, 0.8, 50);
|
||||
window.interiorLight.position.set(0, 0, 0); // Will be moved to model center
|
||||
scene.add(window.interiorLight);
|
||||
|
||||
// Additional interior light for better coverage - increased intensity
|
||||
window.interiorLight2 = new THREE.PointLight(0xffffff, 0.5, 30);
|
||||
window.interiorLight2.position.set(5, 5, -5); // Will be adjusted relative to model
|
||||
scene.add(window.interiorLight2);
|
||||
|
||||
// Hemisphere light for natural lighting - increased intensity
|
||||
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.8);
|
||||
scene.add(hemiLight);
|
||||
|
||||
// Create environment map for glass reflections
|
||||
setupEnvironmentMap();
|
||||
}
|
||||
|
||||
function setupEnvironmentMap() {
|
||||
// Create a simple cube texture for reflections (makes glass more visible)
|
||||
const cubeTextureLoader = new THREE.CubeTextureLoader();
|
||||
|
||||
// Create a procedural cube map for reflections
|
||||
const envMapRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
|
||||
generateMipmaps: true,
|
||||
minFilter: THREE.LinearMipmapLinearFilter
|
||||
});
|
||||
|
||||
// Simple gradient environment for reflections
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 256;
|
||||
canvas.height = 256;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Create gradient for reflections
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 256);
|
||||
gradient.addColorStop(0, '#87CEEB'); // Sky blue
|
||||
gradient.addColorStop(0.5, '#F0F8FF'); // Alice blue
|
||||
gradient.addColorStop(1, '#4682B4'); // Steel blue
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, 256, 256);
|
||||
|
||||
// Convert to texture
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
texture.mapping = THREE.EquirectangularReflectionMapping;
|
||||
|
||||
// Store for glass materials
|
||||
window.environmentMap = texture;
|
||||
}
|
||||
|
||||
function loadCubeModel() {
|
||||
// Create loaders
|
||||
const mtlLoader = new MTLLoader();
|
||||
const objLoader = new OBJLoader();
|
||||
|
||||
// Load material file first
|
||||
mtlLoader.load('cube.mtl', function(materials) {
|
||||
materials.preload();
|
||||
|
||||
// Configure materials for double-sided rendering with proper transparency
|
||||
Object.keys(materials.materials).forEach(function(key) {
|
||||
const material = materials.materials[key];
|
||||
material.side = THREE.DoubleSide;
|
||||
|
||||
// Handle glass materials specially
|
||||
if (key.toLowerCase().includes('glass')) {
|
||||
material.transparent = true;
|
||||
|
||||
// Preserve MTL file opacity values for glass materials
|
||||
// Only apply default opacity if not already set in MTL
|
||||
if (material.opacity === undefined || material.opacity === 1.0) {
|
||||
material.opacity = 0.7; // Fallback for visibility
|
||||
}
|
||||
|
||||
// Enhanced glass properties for better visibility
|
||||
material.shininess = 100;
|
||||
material.reflectivity = 0.8;
|
||||
|
||||
// Glass-like color with slight tint for visibility
|
||||
material.color = new THREE.Color(0.9, 0.95, 1.0);
|
||||
|
||||
// Add specular highlights to make glass more apparent
|
||||
material.specular = new THREE.Color(0.8, 0.9, 1.0);
|
||||
|
||||
// Slight emissive to make glass edges more visible
|
||||
if (material.emissive) {
|
||||
material.emissive.setHex(0x001122); // Very subtle blue glow
|
||||
}
|
||||
|
||||
// Apply environment map for reflections
|
||||
if (window.environmentMap) {
|
||||
material.envMap = window.environmentMap;
|
||||
material.envMapIntensity = 0.9;
|
||||
}
|
||||
|
||||
console.log(`Glass material '${key}' configured with opacity: ${material.opacity}`);
|
||||
} else {
|
||||
// Non-glass materials
|
||||
material.transparent = false;
|
||||
material.opacity = 1.0;
|
||||
|
||||
// Add emissive for interior visibility on solid materials
|
||||
if (material.emissive) {
|
||||
material.emissive.setHex(0x222222); // Increased emissive brightness
|
||||
} else {
|
||||
material.emissive = new THREE.Color(0x222222);
|
||||
}
|
||||
}
|
||||
|
||||
material.needsUpdate = true;
|
||||
});
|
||||
|
||||
// Set materials to OBJ loader
|
||||
objLoader.setMaterials(materials);
|
||||
|
||||
// Load the OBJ file
|
||||
objLoader.load('cube.obj', function(object) {
|
||||
// Scale and position the model
|
||||
object.scale.set(0.5, 0.5, 0.5);
|
||||
object.position.set(0, 0, 0);
|
||||
|
||||
// Enable shadows and fix materials for interior faces
|
||||
object.traverse(function(child) {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
|
||||
// Special handling for glass materials to improve visibility
|
||||
if (child.material && child.material.name && child.material.name.toLowerCase().includes('glass')) {
|
||||
// Glass-specific settings
|
||||
child.material.side = THREE.DoubleSide;
|
||||
child.material.transparent = true;
|
||||
|
||||
// Preserve original opacity from MTL if available
|
||||
if (child.material.opacity === undefined || child.material.opacity === 1.0) {
|
||||
child.material.opacity = 0.7;
|
||||
}
|
||||
|
||||
child.material.alphaTest = 0.1;
|
||||
|
||||
// Enhanced reflective properties
|
||||
child.material.envMapIntensity = 0.9;
|
||||
|
||||
// Apply environment map for reflections
|
||||
if (window.environmentMap) {
|
||||
child.material.envMap = window.environmentMap;
|
||||
}
|
||||
|
||||
// Disable shadow casting for glass (more realistic)
|
||||
child.castShadow = false;
|
||||
child.receiveShadow = true;
|
||||
|
||||
console.log('Applied glass-specific settings to mesh');
|
||||
|
||||
// Add subtle wireframe to glass edges for better visibility
|
||||
if (child.geometry) {
|
||||
const wireframe = new THREE.EdgesGeometry(child.geometry);
|
||||
const wireframeMaterial = new THREE.LineBasicMaterial({
|
||||
color: 0x88aaff,
|
||||
transparent: true,
|
||||
opacity: 0.3,
|
||||
linewidth: 2
|
||||
});
|
||||
const wireframeLines = new THREE.LineSegments(wireframe, wireframeMaterial);
|
||||
child.add(wireframeLines);
|
||||
}
|
||||
} else {
|
||||
// Non-glass materials
|
||||
child.material.side = THREE.DoubleSide;
|
||||
child.material.transparent = false;
|
||||
child.material.opacity = 1.0;
|
||||
|
||||
// Ensure proper lighting on both sides
|
||||
if (child.material.type === 'MeshLambertMaterial' || child.material.type === 'MeshPhongMaterial') {
|
||||
child.material.emissive = new THREE.Color(0x222222); // Increased self-illumination for brightness
|
||||
}
|
||||
}
|
||||
|
||||
child.material.needsUpdate = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Store reference and add to scene
|
||||
cube = object;
|
||||
scene.add(cube);
|
||||
|
||||
// Calculate model center and adjust controls
|
||||
centerModelAndControls(cube);
|
||||
|
||||
// Hide loading indicator
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
|
||||
console.log('Cube loaded successfully!');
|
||||
},
|
||||
function(progress) {
|
||||
console.log('Loading progress:', (progress.loaded / progress.total * 100) + '%');
|
||||
},
|
||||
function(error) {
|
||||
console.error('Error loading OBJ file:', error);
|
||||
document.getElementById('loading').innerHTML =
|
||||
'<div style="color: #ff6b6b;">Error loading model. Please check console for details.</div>';
|
||||
});
|
||||
},
|
||||
function(progress) {
|
||||
console.log('Loading MTL progress:', (progress.loaded / progress.total * 100) + '%');
|
||||
},
|
||||
function(error) {
|
||||
console.error('Error loading MTL file:', error);
|
||||
// Try to load OBJ without materials
|
||||
loadObjWithoutMaterials();
|
||||
});
|
||||
}
|
||||
|
||||
function loadObjWithoutMaterials() {
|
||||
const objLoader = new OBJLoader();
|
||||
|
||||
objLoader.load('cube.obj', function(object) {
|
||||
// Apply default material with better interior visibility
|
||||
const defaultMaterial = new THREE.MeshPhongMaterial({
|
||||
color: 0xffffff,
|
||||
side: THREE.DoubleSide,
|
||||
transparent: false,
|
||||
opacity: 1.0,
|
||||
emissive: new THREE.Color(0x222222), // Increased self-illumination for brightness
|
||||
shininess: 30
|
||||
});
|
||||
|
||||
object.traverse(function(child) {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
child.material = defaultMaterial.clone(); // Clone to avoid sharing
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Scale and position
|
||||
object.scale.set(0.5, 0.5, 0.5);
|
||||
object.position.set(0, 0, 0);
|
||||
|
||||
cube = object;
|
||||
scene.add(cube);
|
||||
|
||||
// Calculate model center and adjust controls
|
||||
centerModelAndControls(cube);
|
||||
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
console.log('Cube loaded without materials!');
|
||||
},
|
||||
function(progress) {
|
||||
console.log('Loading progress:', (progress.loaded / progress.total * 100) + '%');
|
||||
},
|
||||
function(error) {
|
||||
console.error('Error loading OBJ file:', error);
|
||||
document.getElementById('loading').innerHTML =
|
||||
'<div style="color: #ff6b6b;">Failed to load model. Check if cube.obj exists.</div>';
|
||||
});
|
||||
}
|
||||
|
||||
function centerModelAndControls(object) {
|
||||
// Calculate the bounding box of the model
|
||||
const boundingBox = new THREE.Box3().setFromObject(object);
|
||||
|
||||
// Get the center point of the bounding box
|
||||
const center = boundingBox.getCenter(new THREE.Vector3());
|
||||
|
||||
// Get the size of the bounding box
|
||||
const size = boundingBox.getSize(new THREE.Vector3());
|
||||
|
||||
// Set the orbit controls target to the model's center
|
||||
controls.target.copy(center);
|
||||
|
||||
// Calculate optimal camera distance based on model size
|
||||
const maxDimension = Math.max(size.x, size.y, size.z);
|
||||
const fov = camera.fov * (Math.PI / 180);
|
||||
const distance = Math.abs(maxDimension / Math.sin(fov / 2)) * 1.2; // 1.2 for some padding
|
||||
|
||||
// Position camera at optimal distance from model center
|
||||
const direction = camera.position.clone().sub(center).normalize();
|
||||
camera.position.copy(center).add(direction.multiplyScalar(distance));
|
||||
|
||||
// Update camera to look at the model center
|
||||
camera.lookAt(center);
|
||||
|
||||
// Reposition interior lights relative to model center
|
||||
if (window.interiorLight) {
|
||||
window.interiorLight.position.copy(center);
|
||||
|
||||
// Adjust light range based on model size
|
||||
window.interiorLight.distance = maxDimension * 2;
|
||||
}
|
||||
|
||||
if (window.interiorLight2) {
|
||||
window.interiorLight2.position.copy(center).add(new THREE.Vector3(
|
||||
size.x * 0.3,
|
||||
size.y * 0.3,
|
||||
-size.z * 0.3
|
||||
));
|
||||
window.interiorLight2.distance = maxDimension * 1.5;
|
||||
}
|
||||
|
||||
// Update controls
|
||||
controls.update();
|
||||
|
||||
console.log('Model centered at:', center);
|
||||
console.log('Model size:', size);
|
||||
console.log('Camera positioned at:', camera.position);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// Update controls
|
||||
controls.update();
|
||||
|
||||
// Clear and render with proper transparency sorting
|
||||
renderer.clear();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
// Initialize when page loads
|
||||
window.addEventListener('load', init);
|
||||
54
src/styles.css
Normal file
54
src/styles.css
Normal file
@@ -0,0 +1,54 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-family: Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
#container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#info {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
color: white;
|
||||
z-index: 100;
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
#loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
z-index: 200;
|
||||
background: rgba(0,0,0,0.7);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top: 3px solid #ffffff;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
95
webpack.config.js
Normal file
95
webpack.config.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const isProduction = argv.mode === 'production';
|
||||
|
||||
return {
|
||||
entry: './src/index.js',
|
||||
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: isProduction ? '[name].[contenthash].js' : '[name].js',
|
||||
clean: true,
|
||||
publicPath: '/'
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|jpeg|gif|svg)$/i,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: 'assets/images/[name].[hash][ext]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(obj|mtl)$/i,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: 'assets/models/[name].[hash][ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: './src/index.html',
|
||||
filename: 'index.html',
|
||||
minify: isProduction
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: 'cube.obj',
|
||||
to: 'cube.obj'
|
||||
},
|
||||
{
|
||||
from: 'cube.mtl',
|
||||
to: 'cube.mtl'
|
||||
},
|
||||
{
|
||||
from: 'cube/',
|
||||
to: 'cube/'
|
||||
}
|
||||
]
|
||||
})
|
||||
],
|
||||
|
||||
devServer: {
|
||||
static: {
|
||||
directory: path.join(__dirname, 'dist')
|
||||
},
|
||||
compress: true,
|
||||
port: 8080,
|
||||
open: true,
|
||||
hot: true,
|
||||
historyApiFallback: true
|
||||
},
|
||||
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: 'vendors',
|
||||
chunks: 'all'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||
|
||||
resolve: {
|
||||
extensions: ['.js', '.json']
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user