by Samuel Teboul
通过塞缪尔·特布尔
The Angular CLI makes it easy to create an application that already works, right out of the box. It is a great tool, but have you never thought: "How does it work? How can I build an application without the CLI?"
Angular CLI使开箱即用就可以轻松创建已经可以运行的应用程序。 它是一个很棒的工具,但您从未想过: “它如何工作?如何在不使用CLI的情况下构建应用程序?”
Those questions came to my mind when Angular 7 was released. I started to look for answers on the web and what I found was not up-to-date for my purpose. Indeed, as Angular and webpack are always evolving, so dependencies and configurations.
当Angular 7发布时,这些问题浮现在我的脑海。 我开始在网上寻找答案,但发现并不是最新的目的。 确实,由于Angular和webpack一直在发展,因此依赖性和配置也是如此。
In this article you are about to learn:
在本文中,您将学习:
Create a new package.json
file and add the following lines to install Angular and its dependencies.
创建一个新的package.json
文件,并添加以下行以安装Angular及其依赖项。
"dependencies":
"@angular/animations": "~7.0",
"@angular/common": "~7.0",
"@angular/compiler": "~7.0",
"@angular/compiler-cli": "~7.0",
"@angular/core": "~7.0",
"@angular/forms": "~7.0",
"@angular/http": "~7.0",
"@angular/platform-browser": "~7.0",
"@angular/platform-browser-dynamic": "~7.0",
"@angular/platform-server": "~7.0",
"@angular/router": "~7.0",
"@angular/upgrade": "~7.0",
"core-js": "~2.5",
"rxjs": "~6.3",
"zone.js": "~0.8"
}
I have struggled for a long time to find the best folder structure that fits every Angular project, especially when the application grows in size. This article has taught me a lot on the subject.
我一直在努力寻找适合每个Angular项目的最佳文件夹结构,尤其是在应用程序规模增大时。 这篇文章教会了我很多关于该主题的知识。
Create a new src
folder and the following folders/files inside it. All our Angular app business logic will be in this folder.
创建一个新的src
文件夹,并在其中创建以下文件夹/文件。 我们所有的Angular应用业务逻辑都将在此文件夹中。
src
|__ app
|__ modules
|__ menu
|__ components
|__ menu
|__ menu.component.html
|__ menu.component.scss
|__ menu.component.ts
|__ menu.module.ts
|__ menu-routing.module.ts
|__ shared
|__ components
|__ home
|__ home.component.html
|__ home.component.scss
|__ home.component.ts
|__ app.component.html
|__ app.component.scss
|__ app.component.ts
|__ app.module.ts
|__ app-routing.module.ts
|__ index.html
|__ main.ts
Every application has at least one Angular module, the root module that you bootstrap to launch the application. By convention, it is usually called AppModule
. I create another module, the MenuModule
to show you how you can use lazy loading in your project, especially for production.
每个应用程序都有至少一个Angular模块,即您引导启动该应用程序的根模块。 按照惯例,它通常称为AppModule
。 我创建了另一个模块MenuModule
,向您展示如何在项目中使用延迟加载,尤其是在生产中。
Some important points :
一些要点:
index.html
index.html
Add <base href=”/”>
tells our Angular router how to compose navigation URLs . This line means that your app will start from root folder i.e locally it would consider localhost:3000/
and on server it would consider root folder.
添加<base href=”/”>
告诉我们的Angular路由器如何组成导航URL。 这行意味着您的应用将从根文件夹开始,即在本地它将考虑localhost:3000/
而在服务器上将考虑根文件夹。
app-routing.module.ts
app-routing.module.ts
There are three main steps to setting up a lazy loaded feature module:
设置延迟加载的功能模块的主要步骤分为三个步骤:
{path: ‘menu’, loadChildren:’./modules/menu/menu.module#MenuModule’}
tells Angular to lazy load our feature module MenuModule
by the time the user visit the /menu
route.
{path: 'menu', loadChildren:'./modules/menu/menu.module#MenuModule'}
告诉Angular在用户访问/menu
路线时延迟加载我们的功能模块MenuModule
。
Add the following lines to your package.json
file:
package.json
添加到package.json
文件:
"devDependencies": {
"@types/core-js": "~2.5",
"@types/node": "~10.12",
"typescript": "~3.1"
}
Create in your root project folder a tsconfig.json
file:
在您的根项目文件夹中创建一个tsconfig.json
文件:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"lib": ["es6", "dom"],
"typeRoots": ["node_modules/@types"]
},
"exclude": ["node_modules"]
}
This is a basic TypeScript configuration file. It’s essential to install node
and core-js
types definition. Without it, TypeScript won’t be able to compile our Angular application to JavaScript.
这是基本的TypeScript配置文件。 必须安装node
和core-js
类型定义。 没有它,TypeScript将无法将Angular应用程序编译为JavaScript。
First of all, what does compilation means ? It doesn’t mean compiling TypeScript files to JavaScript, this is not related to Angular. Angular itself needs to compile your HTML templates into JavaScript and this can occurred at 2 different points of time:
首先, 编译意味着什么? 这并不意味着将TypeScript文件编译为JavaScript,这与Angular无关。 Angular本身需要将HTML模板编译为JavaScript,这可能会在2个不同的时间点发生:
According to Wikipedia:
根据维基百科:
Webpack is an open source JavaScript module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset. Webpack takes modules with dependencies and generates static assets representing those modules. It’s a module bundler primarily for JavaScript, but it can transform front-end assets like HTML, CSS, even images if the corresponding plugins are included.
Webpack是一个开源JavaScript模块捆绑程序。 它的主要目的是捆绑JavaScript文件以供在浏览器中使用,但它也能够转换,捆绑或打包几乎任何资源或资产。 Webpack接收具有依赖性的模块,并生成代表这些模块的静态资产。 这是一个主要用于JavaScript的模块捆绑器,但如果包括相应的插件,它也可以转换HTML,CSS甚至图像之类的前端资产。
To tell webpack how to bundle our application we have to configure what we call the Core Concepts:
为了告诉webpack如何捆绑我们的应用程序,我们必须配置我们所谓的核心概念 :
Entry — An entry point indicates which module webpack should use to begin building out its internal dependency graph. Webpack will figure out which other modules and libraries that entry point depends on (directly and indirectly).
入口—入口点指示webpack应该使用哪个模块开始构建其内部依赖关系图 。 Webpack将找出入口点所依赖的其他模块和库(直接和间接)。
Output — The output property tells webpack where to emit the bundles it creates and how to name these files. It defaults to ./dist/main.js
for the main output file and to the ./dist
folder for any other generated file.
输出-输出属性告诉webpack在哪里发出它创建的包以及如何命名这些文件。 缺省为./dist/main.js
主输出文件,以及到./dist
任何其他生成的文件夹中。
Loaders — At a high level, loaders have two properties in your webpack configuration:
加载程序-总体而言,加载程序在Webpack配置中具有两个属性:
Plugins — While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management, and injection of environment variables.
插件-使用加载程序来转换某些类型的模块时,可以利用插件来执行更广泛的任务,例如包优化,资产管理和环境变量的注入。
All of these must be set up in the webpack configuration file webpack.config.js.
所有这些都必须在webpack配置文件webpack.config.js.
进行设置webpack.config.js.
In the src
folder we need to create 2 more files:
在src
文件夹中,我们需要再创建2个文件:
vendor.ts
that only imports the application's third-party modules.
仅导入应用程序的第三方模块的vendor.ts
。
polyfills.ts
we need polyfills to run an Angular application in most browsers as explained in the Browser Support guide. This bundle file will load first so this is a good place to configure the browser environment for production or development.
polyfills.ts
我们需要polyfill在大多数浏览器中运行Angular应用程序,如浏览器支持指南中所述。 该捆绑文件将首先加载,因此这是为生产或开发配置浏览器环境的好地方。
Create a new config
folder and the following files inside:
创建一个新的config
文件夹,并在其中包含以下文件:
webpack.config.common.js
: configuration that we will use for development and production.
webpack.config.common.js
:我们将用于开发和生产的配置。
Entry — For this application (and for most of them actually) we have 3 different entry points : vendor.ts
polyfills.ts
and main.ts.
入口-对于这种应用(和大部分其实)我们有3个不同的切入点: vendor.ts
polyfills.ts
和main.ts.
entry: {
vendor: './src/vendor.ts',
polyfills: './src/polyfills.ts',
main: './src/main.ts'
}
Loaders — We load .html
files with html-loader
which is pretty standard. Loading .scss
files is a bit tricky for an Angular app and I struggled for many hours to figure out how to do it.
html-loader
-我们使用标准的html-loader
加载.html
文件。 对于Angular应用程序来说,加载.scss
文件有点棘手,我花了好几个小时努力弄清楚该怎么做。
First of all, we must load sass files by using two loaders sass-loader
and css-loader.
If you want to make debugging easy, especially in development mode, it’s really important to add sourceMap: true
as options. In an Angular application we add styles to component by passing a file path to styleUrls
array as follow styleUrls: ["./path/styles.scss"]
but we need to have style as a string and to-string-loader
will do it for us and cast the output to a string.
首先,我们必须使用两个装载程序sass-loader
和css-loader.
来装载sass文件css-loader.
如果要sourceMap: true
调试,尤其是在开发模式下,则添加sourceMap: true
非常重要sourceMap: true
作为选择。 在Angular应用程序中,我们通过将文件路径传递给styleUrls
数组,如下所示,将样式添加到组件中styleUrls: ["./path/styles.scss"]
但是我们需要将样式作为字符串,并且to-string-loader
将做到这一点对我们来说,将输出转换为字符串。
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(scss|sass)$/,
use: [
'to-string-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
],
include: helpers.root('src', 'app')
}
Plugins — CleanWebpackPlugin
will remove/clean your build folder(s) before building again. HtmlWebpackPlugin
plugin will generate an HTML5 file for you that includes all your webpack bundles in the body using script tags. It only requires path to the template.
插件 CleanWebpackPlugin
将在重新构建之前删除/清除您的构建文件夹。 HtmlWebpackPlugin
插件将使用脚本标签为您生成一个HTML5文件,其中包括您体内的所有webpack捆绑包。 它只需要模板的路径。
new CleanWebpackPlugin(
helpers.root('dist'),
{
root: helpers.root(),
verbose: true
}
),
new HtmlWebpackPlugin({
template: 'src/index.html'
})
webpack.config.dev.js
is our webpack configuration that we will use for development mode only.
webpack.config.dev.js
是我们的webpack配置,仅用于开发模式。
mode: "development"
In webpack 4, chosen mode tells webpack to use its built-in optimizations accordingly.
在webpack 4中,选择的模式告诉webpack相应地使用其内置优化。
devtool: 'cheap-module-eval-source-map'
This option controls if and how source maps are generated. By using cheap-module-eval-source-map
Source Maps from loaders are processed for better results. However, loader Source Maps are simplified to a single mapping per line.
此选项控制是否以及如何生成源映射。 通过使用cheap-module-eval-source-map
处理来自加载程序的Source Map,以获得更好的结果。 但是,加载程序源映射简化为每行一个映射。
output: {
path: helpers.root('dist'),
publicPath: '/',
filename: '[name].bundle.js',
chunkFilename: '[id].chunk.js'
}
The output
key contains a set of options instructing webpack on how and where it should output your bundles, assets and anything else you bundle or load with webpack. Here we tell webpack to output our bundles to the dist
folder.
output
键包含一组选项,这些选项指示webpack如何以及在何处输出捆绑包,资产以及您通过webpack捆绑或加载的其他任何内容。 在这里,我们告诉webpack将包输出到dist
文件夹。
optimization: {
noEmitOnErrors: true
}
Skips the emitting phase whenever there are errors while compiling. This ensures that no erroring assets are emitted. The optimization
key has many others options that are set by default depending on your webpack configuration mode (development/production). You can read more about it here.
编译时只要有错误就跳过发射阶段。 这样可以确保不会发出错误的资产。 optimization
密钥还有许多其他选项,这些选项默认设置,具体取决于您的Webpack配置模式(开发/生产)。 您可以在此处了解更多信息。
{
test: /\.ts$/,
loaders: [
'babel-loader',
{
loader: 'awesome-typescript-loader',
options: {
configFileName: helpers.root('tsconfig.json')
}
},
'angular2-template-loader',
'angular-router-loader'
],
exclude: [/node_modules/]
}
angular-router-loader
is a webpack loader that enables string-based module loading with the Angular Router.
angular-router-loader
是一个webpack加载器,可通过Angular Router加载基于字符串的模块。
angular2-template-loader
is a chain-to loader that inlines all html and styles in Angular components.
angular2-template-loader
是一个链接到加载器,可以内联Angular组件中的所有html和样式。
awesome-typescript-loader
is currently the faster webpack TypeScript loader. It uses dependency resolution to build modules dependency graph. This relatively speeds up the build process.
awesome-typescript-loader
typescript awesome-typescript-loader
当前是速度更快的webpack TypeScript加载器。 它使用依赖关系解析来构建模块依赖关系图。 这相对加快了构建过程。
babel-loader
allows transpiling JavaScript files.
babel-loader
允许转储JavaScript文件。
devServer: {
historyApiFallback: true,
stats: 'minimal'
}
When using the HTML5 History API, the index.html
page will likely have to be served in place of any 404
responses. For that we need to enable historyApiFallback.
使用HTML5历史记录API时 ,可能必须提供index.html
页面来代替任何404
响应。 为此,我们需要启用historyApiFallback.
stats
option lets you precisely control what bundle information gets displayed. This can be a nice middle ground if you want some bundle information, but not all of it.
stats
选项使您可以精确控制显示哪些捆绑软件信息。 如果您需要一些捆绑软件信息,但又不是全部,这可能是很好的中间立场。
Add the following lines to your package.json
file:
package.json
添加到package.json
文件:
"scripts": {
"build:dev": "webpack-dev-server --inline --hot --progress --port 8080"
}
--hot
enables webpack Hot Module Replacement (HMR). It exchanges, adds, or removes modules while an application is running, without a full reload. This can significantly speed up development in a few ways:
--hot
启用webpack热模块更换(HMR)。 它在应用程序运行时交换,添加或删除模块 ,而无需完全重新加载。 这可以通过以下几种方式显着加快开发速度:
Now you are all setup! You can run npm run build:dev
open your browser and navigate to localhost:8080.
现在,您已经完成所有设置! 您可以运行npm run build:dev
打开浏览器并导航到localhost:8080.
In your config
folder create a new file webpack.config.prod.js
在您的config
文件夹中创建一个新文件webpack.config.prod.js
mode: 'production'
We usually proceed to AoT compilation in production mode and, as I wrote previously, in webpack 4, chosen mode tells webpack to use its built-in optimizations accordingly.
我们通常在生产模式下进行AoT编译,正如我之前写的那样,在webpack 4中,选择的模式告诉webpack相应地使用其内置优化。
output: {
path: helpers.root('dist'),
publicPath: '/',
filename: '[hash].js',
chunkFilename: '[id].[hash].chunk.js'
}
We also tell webpack to output our bundles to the dist
folder. We include a hash to the file names to leverage client level cache efficiently. This way webpack knows whether or not a file has changed. Webpack provides placeholders for this purpose. These strings are used to attach specific information to outputs. The most valuable ones are:
我们还告诉webpack将我们的包输出到dist
文件夹。 我们在文件名中包含哈希,以有效利用客户端级别的缓存。 Webpack通过这种方式知道文件是否已更改。 Webpack为此提供了占位符 。 这些字符串用于将特定信息附加到输出。 最有价值的是:
[id]
returns the chunk id.
[id]
返回块ID。
[path]
returns the file path.
[path]
返回文件路径。
[name]
returns the file name.
[name]
返回文件名。
[ext]
returns the extension. [ext]
works for most available fields.
[ext]
返回扩展名。 [ext]
适用于大多数可用字段。
[hash]
returns the build hash. If any portion of the build changes, this changes as well.
[hash]
返回构建哈希。 如果构建的任何部分发生更改,则此更改也将更改。
[chunkhash]
returns an entry chunk-specific hash. Each entry
defined in the configuration receives a hash of its own. If any portion of the entry changes, the hash will change as well. [chunkhash]
is more granular than [hash]
by definition.
[chunkhash]
返回特定于条目块的哈希。 配置中定义的每个entry
接收自己的哈希。 如果条目的任何部分发生更改,则哈希也将更改。 根据定义, [chunkhash]
比[hash]
更细。
[contenthash]
returns a hash generated based on content.
[contenthash]
返回基于内容生成的哈希。
It’s preferable to use particularly hash
and chunkhash
only for production as hashing is not essential during development.
最好仅将hash
和chunkhash
hash
仅用于生产,因为在开发过程中哈希不是必需的。
optimization: {
noEmitOnErrors: true,
splitChunks: {
chunks: 'all'
},
runtimeChunk: 'single',
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true
}),
new OptimizeCSSAssetsPlugin({
cssProcessor: cssnano,
cssProcessorOptions: {
discardComments: {
removeAll: true
}
},
canPrint: false
})
]
}
chunks: ‘all’
indicates which chunks will be selected for optimization. Providing all
can be particularly powerful, because it means that chunks can be shared even between async and non-async chunks.
chunks: 'all'
指示将选择哪些块进行优化。 提供all
功能特别强大,因为这意味着即使在异步和非异步块之间也可以共享块。
Imported modules are initialized for each runtime chunk separately. As webpack suggests, while working on a project with multiple entry points you want to have only one runtime instance. For that you need to set it to ‘single’
.
分别为每个运行时块初始化导入的模块。 正如webpack所建议的那样,在处理具有多个入口点的项目时,您只希望有一个运行时实例。 为此,您需要将其设置为'single'
。
UglifyJsPlugin
uses uglify-js to minify your JavaScript files. We set cache
and parallel
properties to true
in order to enable file caching and to use multi-process parallel running to improve the build speed. There are more options available and I invite you to learn more about this plugin.
UglifyJsPlugin
使用UglifyJsPlugin
-js来最小化您JavaScript文件。 我们将cache
和parallel
属性设置为true
以便启用文件缓存并使用多进程并行运行来提高构建速度。 有更多可用选项,我邀请您进一步了解此插件 。
OptimizeCSSAssetsPlugin
will search for CSS assets during the webpack build and will optimize and minimize it. The CSS processor used for optimization is cssnano.
All comments will be removed from our minified CSS and no messages will be print to the console.
OptimizeCSSAssetsPlugin
将在Webpack构建期间搜索CSS资产,并将其优化和最小化。 用于优化CSS处理器是cssnano.
所有评论将从我们缩小CSS中删除,并且不会将任何消息打印到控制台。
module: {
rules: [
{
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
loader: '@ngtools/webpack'
}
]
}
plugins: [
new ngw.AngularCompilerPlugin({
tsConfigPath: helpers.root('tsconfig.aot.json'),
entryModule: helpers.root('src', 'app', 'modules', 'app', 'app.module#AppModule')
})
]
@ngtools/webpack
is the official plugin that AoT compiles your Angular components and modules. The loader works with webpack plugin to compile your TypeScript. It’s important to include both, and to not include any other TypeScript compiler loader.
@ngtools/webpack
是AoT编译Angular组件和模块的官方插件。 加载程序与webpack插件一起编译您的TypeScript。 重要的是要同时包含两者,并且不包含任何其他TypeScript编译器加载器。
In the src
folder add main.aot.ts
file:
在src
文件夹中添加main.aot.ts
文件:
import { enableProdMode } from '@angular/core';
import { platformBrowser } from '@angular/platform-browser';
import { AppModuleNgFactory } from './app/app.module.ngfactory';
enableProdMode();
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
Your main
entry is a bit different in production mode and AoT compilation:
您的main
条目在生产模式和AoT编译中有些不同:
Import enableProdMode
to disable Angular’s development mode, which turns off assertions and other checks within the framework.
导入enableProdMode
以禁用Angular的开发模式,该模式将关闭框架中的断言和其他检查。
Import platformBrowser
AND NOT platformBrowserDynamic
because in AoT compilation your application is shipped to the browser already compiled whereas in JiT compilation it occurs at the browser level.
导入platformBrowser
不是 platformBrowserDynamic
因为在AoT编译中,您的应用程序已交付给已经编译的浏览器,而在JiT编译中,它发生在浏览器级别。
Instead of importing AppModule
you need to import AppModuleFactory
which is your compiled application generated by our Angular compiler.
无需导入AppModule
您需要导入AppModuleFactory
,这是由Angular编译器生成的已编译应用程序。
Add the following scripts to your package.json
file :
将以下脚本添加到package.json
文件:
"webpack-prod": "cross-env NODE_ENV=production webpack --mode production"
"build:prod": "npm run build:clean && ngc && npm run webpack-prod && npm run build:clean"
"build:clean": "del-cli 'src/**/*.js' 'src/**/*.js.map' 'src/**/*.ngsummary.json' 'src/**/*.metadata.json' 'src/**/**/*.ngfactory.ts' 'src/**/*.ngstyle.ts' 'src/**/*.shim.ts'"
"serve": "lite-server"
build:clean
: the Angular compiler generates many files in order to compile your application. To stay clean in our project, we delete all these files before compilation and after generating bundles.
build:clean
:Angular编译器生成许多文件来编译您的应用程序。 为了保持项目干净,我们在编译之前和生成捆绑包后删除了所有这些文件。
build:prod
: run the Angular compiler with ngc
command and then run webpack in production mode to generate your bundles.
build:prod
:使用ngc
命令运行Angular编译器,然后在生产模式下运行webpack生成捆绑包。
serve
: I use lite-server to serve our application and see what it looks like. Of course, you won’t need it in a real world project because your app will be serve by the cloud.
serve
:我使用lite-server服务我们的应用程序,然后看它看起来像什么。 当然,在现实世界的项目中您将不需要它,因为您的应用程序将由云服务。
Now, you can run npm run build:prod
to compile your Angular application and build your bundles. Then, run npm run serve
to serve your app to the browser.
现在,您可以运行npm run build:prod
来编译Angular应用程序并生成捆绑包。 然后,运行npm run serve
将您的应用程序提供给浏览器。
I hope you enjoyed this article! If you have any questions/suggestions, let me know in the comments below.
希望您喜欢这篇文章! 如果您有任何疑问/建议,请在下面的评论中告诉我。
The project files are on my GitHub:
项目文件在我的GitHub上:
samteb/Angular-7-Webpack-4Contribute to samteb/Angular-7-Webpack-4 development by creating an account on GitHub.github.co
samteb / Angular-7-Webpack-4 通过在GitHub上创建一个帐户来为samteb / Angular-7-Webpack-4开发做出贡献。 github.co