最新のAngularフロントエンドアプリケーションには、次の機能が含まれている必要があります。
- 型付きJSを使用する機能-Typescript
- HMR (ホットモジュール交換)を使用して開発の利便性と生産性を確保します。
- アプリケーションのモジュール性とモジュールの遅延ロードの可能性(遅延ロード);
- AoT-アプリケーションのパフォーマンスを改善するモード(事前)。
これらの問題を解決する多くのビルドオプションがあります(angular cli、A2シードなど)。 通常、それらは複雑な構造を持ち、調整/拡張が不十分で、変更できないモノリスです。
この記事では、Angular 2+とwebpackを組み合わせて、アセンブリ/開発のすべての段階に対処する方法を説明します。
驚くほど簡単です。
最終アプリケーション 。
微妙なポイントを最大限に強調してみます。 行きましょう。
1)プロジェクトを作成する
誰も推測しないようにプロジェクトフォルダーを作成します。これをangle-projectと呼びましょう。
(私はWebstormを使用していますが、エディターでも同じことができます)
2)環境
node.jsをインストールします(デフォルトではバンドルにnpm )。
package.jsonを作成します。もちろん、プロジェクトに接続されているモジュールの数は無限になる可能性がありますが、本格的な開発に必要なものだけを残します。 多くのモジュールがありますが、それらがなぜ必要なのかを正当化しようとします。
package.json
{ "name": "angular-project", "version": "1.0.0", "description": "angular scaffolding", "author": "maxim1006", "license": "MIT", "dependencies": { // Angular "@angular/animations": "^4.3.6", "@angular/common": "^4.3.6", "@angular/compiler": "^4.3.6", "@angular/compiler-cli": "^4.3.6", "@angular/core": "^4.3.6", "@angular/forms": "^4.3.6", "@angular/http": "^4.3.6", "@angular/platform-browser": "^4.3.6", "@angular/platform-browser-dynamic": "^4.3.6", "@angular/router": "^4.3.6", // hmr "@angularclass/hmr": "^2.1.1", "@angularclass/hmr-loader": "^3.0.2", //polyfills es5 "core-js": "^2.5.0", // "reflect-metadata": "^0.1.8", // "rxjs": "^5.4.3", // . js "typescript": "2.3.4", // js, , "zone.js": "^0.8.17" }, "devDependencies": { // AoT (Ahead-of-Time Compilation) angular "@ngtools/webpack": "^1.6.2", // , typescript "@types/es6-shim": "^0.31.35", "@types/jasmine": "^2.5.54", "@types/node": "^7.0.43", //routing "angular-router-loader": "^0.6.0", // "angular2-template-loader": "^0.6.2", // css "autoprefixer": "^6.3.7", // typescript webpack "awesome-typescript-loader": "^3.2.3", // / "copy-webpack-plugin": "^4.0.1", // css "css-loader": "^0.28.5", "css-to-string-loader": "^0.1.2", //es6 polyfills "es6-shim": "^0.35.1", // "hammerjs": "^2.0.8", // webpack html "html-webpack-plugin": "^2.29.0", // "less": "^2.7.2", "less-loader": "^4.0.3", // "on-build-webpack": "^0.1.0", // webpack "raw-loader": "^0.5.1", // "postcss-loader": "^1.3.3", "style-loader": "^0.17.0", // "tslint": "^5.7.0", // - "rimraf": "^2.6.1", // css base64 "url-loader": "^0.5.8", //webpack "webpack": "^3.5.5", // express "webpack-dev-server": "^2.7.1" }, // npm run __command__ ( npm run serve) ) "scripts": { // . , . . - . , ( . .), ( --profile); , webpack , (--watch); , ( –-progress). "serve": "webpack-dev-server --config ./webpack.config.js --profile --watch --progress", // , serve, "hmr": "webpack-dev-server --config ./webpack.config.js --profile --watch --progress", // prod "prod": "npm run aot", // prod, "prodServer": "webpack-dev-server --config ./webpack.config.js --open", // ./dist "clean": "rimraf ./dist", //, webpack.js , aot. , . "aot": "webpack", // "test": "karma start" } }
3)モジュールのインストール
ターミナルから、package.jsonがあるフォルダーに移動し、 npm iコマンドを入力します。
4)グローバルモジュールのインストール
ターミナルではrimraf、webpackおよびwebpack-dev-serverコマンドを使用するため、 npm i rimraf webpack webpack-dev-server -gコマンドを使用してPCにそれらを説明する必要があります。
これらの操作の後、プロジェクトにnode_modulesフォルダーが追加されました。
5)README.md
README.mdを作成します。この記事へのリンクに加えて、プロジェクト開発の機能を追加できます。
6)リンター
tslint.jsonを作成しますが、 特効薬がないのでここでやめません。
tslint.json
{ "rules": { "no-unused-variable": true, "curly": true, "no-console": [ true, "log", "error", "debug", "info" ], "no-debugger": true, "no-duplicate-variable": true, "no-eval": true, "no-invalid-this": true, "no-shadowed-variable": true, "no-unsafe-finally": true, "no-var-keyword": true, "triple-equals": [ true, "allow-null-check", "allow-undefined-check" ], "semicolon": [ true, "always", "ignore-interfaces" ], "variable-name": [ true, "ban-keywords", "check-format", "allow-leading-underscore" ] } }
7)PostCss
スタイルプレフィックスを記述しないようにpostcss.config.jsを作成します
postcss.config.js
module.exports = { plugins: [ require('autoprefixer')({ browsers: [ 'last 2 versions' ], cascade: true }) ] };
もう少し複雑な操作はさらに進みますので、注意してください。
8)Typescript tsconfig.jsonのセットアップ
私の意見では、A2 +の開発はtypescriptなしでは不可能なので、設定する必要があります。 設定は正常ですが、質問がある場合はコメントで質問してください。
tsconfig.json
{ // typescript "compilerOptions": { "target": "es5", "module": "es2015", "declaration": false, "moduleResolution": "node", "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "removeComments": false, "noImplicitAny": false, "suppressImplicitAnyIndexErrors": true, "skipLibCheck": true, "lib": ["es6", "dom"], "outDir": "./dist/", "typeRoots": [ "./node_modules/@types/" ] }, "compileOnSave": false, "buildOnSave": false, // ./src "include": [ "./src/**/*" ], // typescript : "exclude": [ "node_modules/*", "dist/*", "dist-serve/*", "node/*", "**/*.spec.ts" ], // loader webpack "awesomeTypescriptLoaderOptions": { "forkChecker": true, "useWebpackText": true, "useCache": true }, // AoT "angularCompilerOptions": { "genDir": ".", "skipMetadataEmit" : true } }
9)Webpackセットアップ
最も難しいのは、webpackに、私たちが望むものを理解させることです。 これを行うには、パニックなしでwebpack.conf.jsを作成し、すべてを説明しようとします
webpack.conf.js
"use strict"; // node webpack , const path = require('path'); const fs = require('fs'); const webpack = require('webpack'); const WebpackOnBuildPlugin = require('on-build-webpack'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const AotPlugin = require('@ngtools/webpack').AotPlugin; //, package.json serve, hmr, prod . .? , (, npm run serve, 'serve') : const ENV = process.env.npm_lifecycle_event ? process.env.npm_lifecycle_event : ''; const isStatic = ENV === 'serve'; const isHmr = ENV === 'hmr'; const isProd = ENV === 'prod'; const isTest = ENV === 'test'; const isAot = ENV.includes('aot'); const isProdServer = ENV.includes('prodServer'); // , webpack // webpack.conf.js , module.exports = function makeWebpackConfig() { console.log(`You are in ${ENV} mode`); // let config = {}; // // - npm run prodServer, npm run prod, if (isProdServer) { if (!fs.existsSync('./dist')) { throw "Can't find ./dist, please use 'npm run prod' to get it."; } } // sourcemaps if (isHmr || isStatic) { config.devtool = 'inline-source-map'; } else { config.devtool = 'source-map'; } // , webpack. index.html “./ng-app.js” config.entry = { 'ng-app': './src/app/ng-main.ts' }; // AoT , … if (isAot) { config.entry['ng-app'] = './src/app/ng-main-aot.ts'; } // , webpack 'ng-app', filename: '[name].js', prod , './dist', path: root('./dist') config.output = isTest ? {} : { path: root('./dist'), //root – , , webpack.config.js filename: '[name].js' }; // entry webpack - , , prodServer prod . , , , . , webpack.conf.js, webpack-prod-server.js, , , . if (isProdServer) { config.entry = { 'server': './webpack-prod-server.js' }; config.output = {}; } // , webpack config.resolve = { extensions: ['.ts', '.js', '.json', '.html', '.less', '.svg'] }; // loaders: , . , ts js, html js , less css js , 10 base64 js . config.module = { rules: [ { test: /\.ts$/, use: isAot ? [{loader: '@ngtools/webpack'}] : [ { loader: 'awesome-typescript-loader?' }, { loader: 'angular2-template-loader' }, { loader: 'angular-router-loader' } ].concat(isHmr ? '@angularclass/hmr-loader?pretty=' + !isProd + '&prod=' + isProd : []), exclude: [/\.(spec|e2e|d)\.ts$/] }, { test: /\.html$/, loader: 'raw-loader', exclude: [/node_modules\/(?!(ng2-.+))/, root('src/index.html')] }, { test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?name=[name].[ext]&limit=10000&useRelativePath=true" }, { test: /\.less$/, use: [ {loader: "css-to-string-loader"}, {loader: "css-loader"}, {loader: "postcss-loader"}, {loader: "less-loader"} ] } ] }; // , webpack if (!isTest) { config.plugins = [ // webpack warcher new webpack.NoEmitOnErrorsPlugin(), // .ts , .ts new webpack.DefinePlugin({ 'process.env': { 'STATIC': isStatic, 'HMR': isHmr, 'PROD': isProd, 'AOT': isAot } }), // - new WebpackOnBuildPlugin((stats) => { console.log('build is done'); }) ] // hmr, hmr .concat(isHmr ? new webpack.HotModuleReplacementPlugin() : []); } // 'npm run prod', prod AoT if (isAot) { config.plugins = [ // AoT new AotPlugin({ tsConfigPath: './tsconfig.json', entryModule: root('src/app/app.module.ts#AppModule') }), // new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false, screw_ie8: true, conditionals: true, unused: true, comparisons: true, sequences: true, dead_code: true, evaluate: true, if_return: true, join_vars: true }, output: { comments: false }, sourceMap: true }), // ./dist (js webpack, , ) new CopyWebpackPlugin([ {from: 'index.html', context: './src'}, {from: 'assets/themes/base/fonts/**/*', context: './src'}, {from: 'assets/themes/base/images/other-images/**/*', context: './src'}, ]), new WebpackOnBuildPlugin((stats) => { console.log('build in aot is done'); }) ]; } // webpack-dev-server config.devServer = { contentBase: isProdServer ? "./dist" : "./src",// , prod ./dist, ./src headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS", "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization" }, // rest historyApiFallback: true, // HTML5 history api, 1 compress: true,// gzip quiet: false, // inline: isHmr || isStatic || isProdServer, //inline mode hot: isHmr, // hmr, hmr stats: "minimal", port: 9000, // Webpack overlay: { errors: true }, // webpack warcher watchOptions: { aggregateTimeout: 50, ignored: /node_modules/ } }; return config; }; // function root(__path = '.') { return path.join(__dirname, __path); }
10)src構造
srcフォルダーを除いて、プロジェクトは次のようになります。
srcフォルダーに構造を作成します。
いくつかのコメント: アプリフォルダーには角度アプリケーションがあり、 アセットフォルダーには補助ファイル、 index.htmlがsrcに配置されています。 アセットでは、テーマをサポートし、フォルダーを分割して、フォント、画像、スタイルを簡単に操作できます。
私たちの会社では、私たちの意見では、少し修正され、より最適なBEM方法論を使用しています。 base.less-ベーステーマの集約.lessファイル:
base.less
// Common @import "themes/base/styles/common/normalize"; @import "themes/base/styles/common/colors"; @import "themes/base/styles/common/common"; @import "themes/base/styles/common/fonts"; @import "themes/base/styles/common/vars"; // Blocks // (please, add new blocks in alphabetical order) @import "themes/base/styles/blocks/app-component";
私たちの意見では、アプリケーションの機能部分とスタイル部分を区別する必要があることに注意してください。これにより、アセンブリとプロジェクトの両方のサポートの多くの問題が解決されます。 BEMと1つのブロックというパラダイムを使用すると、ファイルが1つ少なくなり、アプローチに問題はありません。 ただし、多くの選択肢があります。 この投稿の付録で、資産をさらに詳しく掘り下げることができます。 記事へのコメントで質問してください。
11)index.hml
index.html -A2 +アプリケーションで非常にシンプルになりました
index.html
<!DOCTYPE html> <html> <head> <base href="/"> // A2+ routing <meta charset="utf-8"> <title>Landing</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="/img/favicon.ico"> </head> <body> <app-component>Loading...</app-component> <script type="text/javascript" src="./ng-app.js"></script> </body> </html>
12)角度アプリ
深呼吸してください、私たちはすでにすべての複雑なことをしました、今フレームワーク自体は残ります)
appフォルダーに構造を作成します。
一見-リバス。
ただし、少なくともAngular 2+チュートリアルを完了している場合は、これらすべてがすでによく知られています。 残りの短いコメント:アプリケーション全体がモジュールに分割され、フレームワークはそのようなエンティティ-モジュールを提供します。 メインモジュール-app.module.tsがあり、アプリケーションの機能を拡張する追加モジュールがあります。 ほとんどのアプリケーションには、ホームモジュール、遅延モジュール、共有モジュールがあります。 モジュールの名前はもちろんオプションですが、命名規則に従えば、アプリケーションの拡張性に問題はありません。
フレームワーク自体についてはあまり触れません。優れたドキュメントがあります 。 微妙な点に焦点を当てる:
ng-main.ts
それはすべて彼から始まります
ng-main.ts
import './ng-polyfills'; // ie 9+ import … // webpack , if (process.env.STATIC) { //console.log("******************You are in Dev mode******************"); platformBrowserDynamic().bootstrapModule(AppModule).then(():any => {}); } else if (process.env.HMR) { // hmr Angular //console.log("******************You are in HMR mode******************"); bootloader(main); } export function main() { return platformBrowserDynamic() .bootstrapModule(AppModule) }
AoTのng-main-aot.ts
AoT (Ahead-of-Time Compilation)モードの場合、別のメインファイルng-main-aot.tsを作成します。
ng-main-aot.ts
import … console.log("******************You are in prod mode******************"); enableProdMode(); platformBrowser() .bootstrapModuleFactory(<any>AppModuleNgFactory) .catch(error=>console.log(error));
Hmr、スタイル、hammerjs
HMR、アプリケーションのスタイル(念のため、画像を接続する例を残しました)、およびモバイル開発用のhammerjs設定は、この方法でapp.module.tsに接続されます。
app.module.ts
require("style-loader!../assets/base.less"); // webpack import … // hammer.js export class MyHammerConfig extends HammerGestureConfig { overrides = <any>{ 'swipe': {velocity: 0.4, threshold: 20} } } @NgModule({ declarations: [ AppComponent, ], imports: [ BrowserModule, HomeModule, NgRoutingModule ], providers: [ ], bootstrap: [ AppComponent ] }) export class AppModule { constructor(public appRef: ApplicationRef) {} hmrOnInit(store) { if (!store || !store.state) return; if ('restoreInputValues' in store) { store.restoreInputValues(); } this.appRef.tick(); delete store.state; delete store.restoreInputValues; } hmrOnDestroy(store) { let cmpLocation = this.appRef.components.map(cmp => cmp.location.nativeElement); store.disposeOldHosts = createNewHosts(cmpLocation); store.state = {data: 'yolo'}; store.restoreInputValues = createInputTransfer(); removeNgStyles(); } hmrAfterDestroy(store) { store.disposeOldHosts(); delete store.disposeOldHosts; } }
遅延読み込み
遅延読み込みモジュールはng-routing.module.tsにプラグインします
ng-routing.module.ts
import … const routes: Routes = [ {path: '', redirectTo: '/home', pathMatch: 'full'}, {path: 'home', component: HomeComponent}, // lazy , .js webpack {path: 'lazy', loadChildren: './modules/lazy/lazy.module#LazyModule'}, {path: '**', component: PageNotFoundComponent}, ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export class NgRoutingModule { }
ルーターで遅延モジュールを接続した後、遅延をロードするモジュールで(lazy.module.tsの例を使用して)次のことが必要です。
lazy.module.ts
import … const routes: Routes = [ {path: '', component: LazyComponent}, ]; @NgModule({ imports: [SharedModule, RouterModule.forChild(routes)], exports: [LazyComponent], declarations: [LazyComponent] }) export class LazyModule {}
うーん...まあ、それは基本的にそれです。 この投稿のアプリケーション内のアプリフォルダーを掘り下げることができます。
エディターでコードを変更するたびにページをリロードする開発では、package.jsonのフォルダーにあるターミナルに書き込みます: npm run serve
同じですが、ページをリロードせずに: npm run hmr
AoTでprodをビルドする: npm run prod
prodを監視するために静的サーバーを起動します: npm run prodServer
./distフォルダーのクリーン: npm run clean
ほんの数ステップで動作します: Angular 4、AoT、HMR、Lazy loadingを使用したwebpackビルド 。 テンプレートやスタイルなど、すべてがきちんとバンドルに入れられ、最適化されます。
もちろん、この構成は拡張、改善、変更できますが、私の意見では、Angular 2+で安全に開発を開始するには十分です。
PS
AoTの小さな広告:Angular SPAアプリケーションのパフォーマンスを大幅に向上させます。
ご清聴ありがとうございました。