无需手动添加import,如何用Webpack打包单命名空间AngularJS TypeScript应用?
Great question—this is a super common scenario when migrating legacy AngularJS apps to modern bundlers like Webpack. The good news is you don’t have to manually add thousands of import statements right away. Let’s break down your options:
You can get Webpack to replicate your Grunt file-splicing behavior by treating all your files as global scripts, not modules. Here are two reliable approaches:
Use an Entry Array with Glob Matching
Webpack’s entry accepts an array of file paths, which it will bundle in the exact order you specify. To avoid listing thousands of files manually, use a glob utility to auto-match all your TypeScript files—just make sure to preserve the dependency order your app relies on (matching what your Grunt config did).
First, install glob if you don’t have it:
npm install glob --save-dev
Then configure your webpack.config.js like this:
const glob = require('glob'); const path = require('path'); module.exports = { entry: [ // Load AngularJS first (assuming it's installed via npm) 'angular', // Load your root namespace definition first—critical for other files to attach to it path.resolve(__dirname, 'src/app/namespace-root.ts'), // Load all other TS files, excluding the root namespace to avoid duplication ...glob.sync(path.resolve(__dirname, 'src/app/**/*.ts')).filter(file => file !== path.resolve(__dirname, 'src/app/namespace-root.ts') ) ], output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.ts$/, use: 'ts-loader', exclude: /node_modules/ } ] }, resolve: { extensions: ['.ts', '.js'] } };
Pro tip: If your app has strict dependency order requirements (e.g., services must load before components), tweak the glob pattern or add separate glob groups to enforce that order. For example:
entry: [ 'angular', './src/app/namespace-root.ts', ...glob.sync('./src/app/services/**/*.ts'), ...glob.sync('./src/app/components/**/*.ts'), ...glob.sync('./src/app/**/*.ts').filter(file => !file.includes('services') && !file.includes('components')) ]
Use script-loader to Execute Files Globally
If you prefer to define an explicit entry file rather than relying on glob order, use script-loader to load each file as a global script (just like Grunt’s concatenation). This ensures each file runs in the global scope, so your namespace members get attached to window as expected.
Install the loader first:
npm install script-loader --save-dev
Then create an entry file (e.g., main.ts) that loads all your app files in order:
// main.ts // Load AngularJS import 'angular'; // Load root namespace import 'script-loader!./src/app/namespace-root.ts'; // Load services import 'script-loader!./src/app/services/auth.service.ts'; import 'script-loader!./src/app/services/data.service.ts'; // Load components import 'script-loader!./src/app/components/header.component.ts'; // ... repeat for all files
While this requires listing files, you can automate this with a simple Node script that scans your directory and generates this entry file for you (more on that below).
If you want to eventually move to proper modules but can’t rewrite everything at once, here are ways to reduce the manual effort:
Auto-Generate Entry Files with a Custom Script
Write a quick Node script to scan your src/app directory, collect all .ts files in the correct order, and generate an entry file with script-loader imports (or even regular imports if you’re ready to start modularizing).
Example script (generate-entry.js):
const fs = require('fs'); const path = require('path'); const rootDir = path.resolve(__dirname, 'src/app'); const outputFile = path.resolve(__dirname, 'src/main.ts'); // Define your file order priority (match your Grunt config) const priorityDirs = ['services', 'components', 'directives', 'filters']; let fileList = ['import \'angular\';', 'import \'script-loader!./namespace-root.ts\';']; // Add priority directories first priorityDirs.forEach(dir => { const dirPath = path.join(rootDir, dir); if (fs.existsSync(dirPath)) { const files = fs.readdirSync(dirPath).filter(file => file.endsWith('.ts')); files.forEach(file => { fileList.push(`import 'script-loader!./${dir}/${file}';`); }); } }); // Add remaining files const allFiles = fs.readdirSync(rootDir).filter(file => file.endsWith('.ts') && file !== 'namespace-root.ts' && !priorityDirs.includes(file) ); allFiles.forEach(file => { fileList.push(`import 'script-loader!./${file}';`); }); // Write the entry file fs.writeFileSync(outputFile, fileList.join('\n')); console.log(`Entry file generated at ${outputFile}`);
Run this script whenever you add new files:
node generate-entry.js
Gradually Modularize Without Breaking the App
Once you have Webpack working, you can start converting parts of the app to ES modules while keeping compatibility with the global namespace. For example:
- Convert a service to a module:
// src/app/services/auth.service.ts (modular version) export class AuthService { login(username: string, password: string) { // ... existing logic } } // Keep compatibility with the global namespace (window as any).MyApp.Services.AuthService = AuthService;
- Import it in your entry file (or another module) while still letting legacy code access it via
MyApp.Services.AuthService:
import { AuthService } from './src/app/services/auth.service'; // No need for script-loader here anymore
This way, you can migrate files one at a time without rewriting the entire app upfront.
- Preserve File Order: Your legacy app relies on the order files are loaded—make sure Webpack matches this exactly to avoid "undefined" errors.
- TypeScript Config: Ensure your
tsconfig.jsonhasmodule: "esnext"or"commonjs"set, andtargetmatches your app’s current target (e.g.,es5). - Test Incrementally: Test your bundle after each config change to catch order issues early.
内容的提问来源于stack exchange,提问作者Ilya Doroshenko

