Message to administrator
Имя:
Email:
Message:
Sign In
Username:
Password:

Donation  •  Journal  •  About  •  Advertisement  •  Place ads banner  •  Send content  •  Timeline  •  Translate  •  Featured  •  Message to admin Guests: 18    Members: 0 Авторизация Sign In   Sign Up 
Scientific Poke Method
RULVEN
Search  
Blackball iMag | интернет-журнал
RSS-лента
Share link:
Catalogue


Home » Software development » Angular без CLI (руководство)

Angular без CLI (руководство)руководство


Опубликовано: 20 март 2022 г.
Added: Пн 03.04.2023 • Sergeant
Author: Ivan Kolesov
Source: source
Views: 554
Comments: 0


Фреймворк Angular используется при создании SPA и предлагает большое количество инструментов как для создания, непосредственно, элементов интерфейса, так и CLI для создания и управления структурой файлов, относящихся к приложению.

Для создания проекта с использованием библиотеки Angular, официальный сайт предлагает нам установить пакет angular-cli и далее из консоли запустить определенные команды, которые скачают нужные пакеты, создадут нужные файлы и останется только запустить приложение, однако что если мы не хотим использовать коробочное решение, мы хотим сами создать структуру папок, заполнить ее файлами, подключить нужные библиотеки и собрать, в общем полностью контролировать процесс создания приложения.

Я задался таким вопросом, и, после изучения этого вопроса я собрал это в туториал.

При написании статьи я использовал следующие технологии:

  • Webpack v5
  • Angular v13
  • NodeJS v14
  • NPM v8

Что нужно знать, чтобы понять этот туториал:

  • javascript, typescript
  • webpack, webpack-cli
  • html, css

Особенности:

  • Приложение разрабатывается для браузера
  • Для того, чтобы не потеряться, какую настройку куда добавлять, в некоторых файлах с кодом путь к целевому файлу будет подписан

Итак, приступим.

1. Начнем с того, что создадим каталог с нашим приложением

mkdir angular-no-cli

2. Добавим package.json и typescript

cd ./angular-no-cli
npm init
npm i -D typescript
npx tsc --init

3. Создадим angular-подобную структуру каталогов и добавим основые файлы приложения

mkdir src/app
mkdir src/assets
touch webpack.config.js
touch src/index.css
touch src/index.html
touch src/main.ts
touch src/app/app.component.css
touch src/app/app.component.html
touch src/app/app.component.ts
touch src/app/app.module.ts

4. Добавим необходимые библиотеки.

npm i -D webpack webpack-cli webpack-dev-server
npm i @angular/platform-browser @angular/platform-browser-dynamic @angular/common @angular/core rxjs zone.jse.js
npm i -D ts-loader

Для чего нужны эти библиотеки.

webpack

основной сборщик

webpack-cli

CLI команды для webpack

webpack-dev-server

development сервер для пошаговой разработки

@angular/platform-browser

библиотека для запуска Angular приложений в браузере

@angular/platform-browser-dynamic

библиотека для запуска Angular приложений в браузере с поддержкой JIT компиляции

@angular/common

библиотека с основными элементами для работы приложения: http-клиент, роутинг, локализация, компоненты, пайпы, директивы и.т.д.

@angular/core

библиотека функций, осуществляющая основную функциональность работы приложения: рендеринг, перехват событий, DI и.т.д.

rxjs

библиотека, реализующая Subscriber-Observer поведение, активно используется пакетами angular

zone.js

библиотека, создающая контекст выполнения функций, который сохраняется в асинхронных задачах

ts-loader

библиотека для сборки .ts файлов

5. Добавим базовую конфигурацию для webpack.

// webpack.config.js
const path = require("path");
module.exports = {
    mode: "development",
    devtool: false,
    context: path.resolve(__dirname),
    entry: {
        app: path.resolve(__dirname, "src/main.ts"),
    },
    stats: 'normal',
    output: {
        clean: true,
        path: path.resolve(__dirname, "dist"),
        filename: "[name].js"
    },
    resolve: {
        extensions: [".ts", ".js"]
    },
    // пока будем собирать только ts файлы
    module: {
        rules: [
            {
                test: /\.(js|ts)$/,
                loader: "ts-loader",
                exclude: /node_modules/
            },
        ]
    }
}

6. Добавим базовую конфигурацию для tsconfig.json

{
    "compilerOptions": {
        "target": "es2016",
        "lib": ["es2020", "dom"],
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "module": "ES2020",
        "moduleResolution": "node",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true
    }
}

7. Добавим код в файлы приложения.

// src/main.ts
import "zone.js/dist/zone";
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app/app.module';

platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.error(err));

<!--src/index.html-->
<html lang="ru">
<head>
    <base href="/">
    <title>Angular no cli</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<app-root></app-root>
</body>
</html>

<!--src/app/app.component.html-->
<main>Angular no CLI</main>

// src/app/app.component.ts
import {Component} from "@angular/core";

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html",
    styleUrls: ["app.component.css"]
})
export class AppComponent {
}

// src/main.ts
import {NgModule} from "@angular/core";
import {AppComponent} from "./app.component";
import {BrowserModule} from "@angular/platform-browser";

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule {}

8. Попробуем собрать.

npx webpack

Видим файл сборки по пути dist/app.js

9. Теперь настроим работу dev-server, для этого в конфигурацию webpack добавим следующее.

// webpack.config.js
devServer: {
    static: {
        directory: path.resolve(__dirname, "dist")
    },
    port: 4200,
    hot: true,
    open: false
}

10. Отлично,проверим работу dev-сервера, запустим его.

npx webpack serve

Вместо нашей надписи, мы увидим только ссылку на просмотр файла нашей сборки, кажется мы забыли про index.html, нужно его добавить.

11. За основу возьмем наш src/index.html, чтобы он попал в директорию dist, воспользуемся html-webpack-plugin, установим его и добавим в конфигурацию webpack

npm i -D html-webpack-plugin

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

plugins: [
    new HtmlWebpackPlugin({
        filename: path.resolve(__dirname, "dist", "index.html"),
        template: path.resolve(__dirname, "src/index.html")
    })
]

12. Снова запустим сборку, на этот раз в dist добавится index.html, который будет загружать app.js.

13. Давайте снова запустим dev-server и посмотрим на результат.

14. Мы видим белый фон страницы, произошла ошибка, давайте откроем консоль и посмотрим, что там

GET http://localhost:4200/app.component.html 404 (Not Found)

15. Эта ошибка объясняется тем, что в app.component.ts мы указали параметр templateUrl: "./app.component.html". Соответственно, @angular/core пытается загрузить этот шаблон через обычный HTTP запрос и не находит такого файла.

Тут может возникнуть вопрос, а ведь с использованием CLI мы вообще не видим никаких html файлов в выходной директории, после ng build. Так и есть, это одна из особенностей angular, мы подробнее разберем этот вопрос ниже.

16. Давайте просто скопируем файл шаблона в dist. Мы можем скопировать файл руками, но лучше отдать эту возможность сборщику. Для этого нам понадобиться еще один плагин.

npm i -D copy-webpack-plugin

// webpack.config.js
const CopyPlugin = require("copy-webpack-plugin");

new CopyPlugin({
    patterns: [
        {
            from: "**/*.html",
            to: path.resolve(__dirname, "dist", "[name].html"),
            context: "src/app/"
        }
    ]
})

Здесь мы попросим плагин скопировать все html файлы в репозитории src/app и поместить c текущим именем в dist.

17. Опять такая же ошибка, только теперь для app.component.css файла, мы css файлы пока никак не обрабатываем, давайте просто закомментируем.

// src/app/app.component.ts
// styleUrls: ["app.component.css"]

18. Теперь попробуем добиться схожей структуры файлов в сборочной директории, которую мы обычно видим в проектах, созданных с помощью Angular CLI, список файлов там следующий

  • 3rdpartylicenses.txt - лицензии сторонних библиотек
  • favicon.ico - иконка
  • index.html - основной html файл
  • main.js - код всех необходимых библиотек для запуска и исполнения кода, включая и наш код
  • polyfills.js - полифилы
  • runtime.js - функции загрузки модулей

19. Для начала выделим runtime.js, для этого добавим новую настройку в наш webpack.config

// webpack.config.js
optimization: {
    runtimeChunk: 'single'
}

20. Основной скрипт app.js давайте разделим на основную и venod части

// webpack.config.js
optimization: {
    runtimeChunk: 'single',
    splitChunks: {
        chunks: "all",
        maxAsyncRequests: Infinity,
        minSize: 0,
        name: "vendor"
    }
}

21. Итак, в сборочной директории мы видим несколько javascript файлов и index.html где они все подключаются, на этом моменте можем еще раз собрать и запустить, чтобы убедиться, что все работает

22. Теперь давайте избавимся от копирования шаблонов, сделаем так, чтобы они добавились в javascript код, для этого давайте немного разберем webpack конфигурацию, которая создается, когда мы собираем проект с помощью Angular CLI. Нас интересует, какие загрузчики используются для обработки кода, а также как происходит его оптимизация, по ходу разберем небольшие особенности работы самого Angular.

  1. Для того, чтобы посмотреть последовательность выполнения скриптов, можно просто запустить команду ng build в режиме дебага из пакета angular-cli. В рамках даного туториала делать этого не нужно, здесь я вкратце опишу как все работает.

    npm install -g @angular/cli
    ng new my-first-project
    cd my-first-project
    node --inspect-brk .\node_modules\@angular\cli\bin\ng build

     
  2. Начинается с того, что проверяются версии зависимых пакетов, создаются логгеры.
  3. Потом читается и проверяется файл конфигурации angular.json
  4. Дальше запускается команда с красноречивым именем validateAndRun.

    const command = new description.impl(context, description, logger);
    const result = await command.validateAndRun(parsedOptions);

     
  5. Следующий шаг - запуск задачи на сборку, тут @angular/cli делегирует свою работу другому пакету @angular-devkit, который и начинает строить webpack.config

    buildWebpackBrowser(options, context);
    // options - это объект с настройками angular.json
    // context - объект с утилитными функциями angular

     
  6. Объект конфигурации создается в несколько этапов
    • Сначала запрашивается конфигурация для tsconfig
    • Потом составляется список браузеров, в которых наш код может выполняться
    • Потом выполняются проверки на корректность версий, корректность значений настроек и многое другое, каждая проверка в случае ошибки подробно опишет пользователю, что пошло не так.
       
  7. Вот так выглядит вызов метода, который вернет конфигурацию

    // config - объект конфигурации webpack
    const { config, projectRoot, projectSourceRoot, i18n } =
        await webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext(adjustedOptions, context, (wco) => [
            configs_1.getCommonConfig(wco),
            configs_1.getBrowserConfig(wco),
            configs_1.getStylesConfig(wco),
            configs_1.getTypeScriptConfig(wco),
            wco.buildOptions.webWorkerTsConfig ? configs_1.getWorkerConfig(wco) : {},
        ], { differentialLoadingNeeded });

     
  8. Там нас интересуют загрузчики, плагины и оптимизация кода, давайте постепенно добавим их в нашу конфигурацию

23. Найдем в module.rules правила для загрузки html, javascript или typescript файлов

module: {
    rules: [
      {//*1
            test: /\.?(svg|html)$/,
            resourceQuery: /\?ngResource/,
            type: "asset/source"
    },
    {//*2
        test: "/\.[cm]?[tj]sx?$/",
        resolve: {
            fullySpecified": false
        },
        exclude: ["/[/\\](?:core-js|@babel|tslib|web-animations-js|web-streams-polyfill)[/\\]/"],
        use: [{
            loader: ".../@angular-devkit/build-angular/src/babel/webpack-loader.js",
            options: {
                cacheDirectory: ".../angular/cache/babel-webpack",
                scriptTarget: 4,
                aot: true,
                optimize: true
            }
        }]
    },
    {//*3
        test: "/\.[cm]?tsx?$/",
        loader: "../@ngtools/webpack/src/ivy/index.js",
        exclude: ["/[/\\](?:css-loader|mini-css-extract-plugin|webpack-dev-server|webpack)[/\\]/"]
    }
  ]
}

Нашли несколько загрузчиков:

1 - обработает файлы, попавшие под выражение ".html?ngResource". В качестве загрузчика выступает raw-loader

2 и 3 - обработает javascript и typescript файлы. В качестве загрузчика выступает @angular-devkit/build-angular и @ngtools/webpack. Это то что нам нужно, но перед тем, как добавлять их в нашу конфигурацию, давайте узнаем о них побольше.

24. Попробуем найти репозитории наших загрузчиков на гитхабе

npm repo @ngtools/webpack
npm repo @angular-devkit/build-angular

Оба ведут в корневую репу angular-cli, там их можно найти в поддиректории packages.

build-angular - содержит в себе файлы с лоадером и плагинами для webpack, в комментариях в коде можно найти такое описание: "This package contains Architect builders used to build and test Angular applications and libraries."

ngtools/webpack - тажке видим загрузчик и плагины, но важнее то, что есть файл README, который говорит нам, что это загрузчик, который можно использовать, если мы хотим собрать проект на базе Angular-фреймворка, как раз наш случай. В описании также сказано, что нужно будет подключить babel-loader с Linker Ivy плагином и AngularWebpackPlugin.

25. Давайте установим нужные пакеты, которые советуют в README

# При установке может возникнуть ошибка с peerDependency, который хочет
# определенную версию typescript, можем проигнорировать это и
# добавить флаг --legacy-peer-deps
npm i -D @ngtools/webpack babel-loader @angular/compiler-cli @angular/compiler @angular-devkit/build-angular

# можно сразу удалить, т.к. мы будем использовать другой загрузчик
npm rm ts-loader

26. Итак, после установки давайте изменим поля module.rules и plugins в конфигурации webpack

const AngularWebpackPlugin = require('@ngtools/webpack').AngularWebpackPlugin;

module: {
    rules: [
        {
            test: /\.?(svg|html)$/,
            resourceQuery: /\?ngResource/,
            type: "asset/source"
        },
        {
            test: /\.[cm]?[tj]sx?$/,
            exclude: /\/node_modules\//,
            use: [
                {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true,
                        compact: true,
                        plugins: ["@angular/compiler-cli/linker/babel"],
                    },
                },
                {
                    loader: "@angular-devkit/build-angular/src/babel/webpack-loader",
                    options: {
                        aot: true,
                        optimize: true,
                        scriptTarget: 7
                    }
                },
                {
                    loader: '@ngtools/webpack',
                },
            ],
        },
    }],
    plugins: [
        new AngularWebpackPlugin({
            tsconfig: path.resolve(__dirname, "tsconfig.json"),
            jitMode: false,
            directTemplateLoading: true
        })
    ]

27. Копирование html шаблонов можем закоммментировать, сам плагин нам еще понадобиться, а вот шаблоны нет.

/* new CopyPlugin({
    patterns: [
        {
            from: "**!/!*.html",
            to: path.resolve(__dirname, "dist", "[name].html"),
            context: "src/app/"
        }
    ]
}),*/

28. На этом моменте можем запустить dev-server, чтобы убедиться, что все собирается, как надо.

29. Давайте теперь добавим стили, css файлы у нас есть, добавим в них правила.

/* файл app.component.css */
main {
    color: red;
}

/* файл index.css */
html {
    background: lightcyan;
}

30. Отлично, теперь поставим нужные лоадеры и плагины для работы со стилями.

npm i -D css-loader mini-css-extract-plugin postcss-loader

31. Давайте раскомментируем ссылку на наши стили в app.component.ts

// файл app.component.ts
styleUrls: ["app.component.css"]

32. Еще немного изменим конфигурацию webback, добавим mini-css-extract-plugin, чтобы экспортировать наши стили в отдельный файл и изменим entry, чтобы подключить сборку стилей.

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

entry: {
    index: ["./src/main.ts", "./src/index.css"]
},
module: [
    rules: {
        test: /\.(css)$/,
        exclude: /\/node_modules\//,
        oneOf: [
            {
                resourceQuery: {
                    not: [/\?ngResource/]
                },
                use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"]
            },
            {
                type: "asset/source",
                loader: "postcss-loader"
            }
        ]
    }
],
plugins: [
    new MiniCssExtractPlugin({
        filename: '[name].css',
    }),
]

33. Снова запустим dev-server, увидим, что наша надпись стала красного цвета, а фон - голубого, продолжим.

34. Давайте добавим любую картинку, допишем код в нашем шаблоне.

<!-- app.component.html-->
<!-- У меня это waiter.svg, положил я его в src/assets/waiter.svg -->
<img src="/assets/waiter.svg" alt="waiter">

35. Раскомментируем CopyPlugin и изменим его конфигурацию, чтобы он добавил наши assets в dist

new CopyPlugin({
    patterns: [
        {
            context: "src/assets/",
            from: "**/*",
            to: "assets/",
        }
    ]
})

36. Снова проверим dev-server, теперь видим и картинку, все работает.

37. Теперь давайте разберем часть webpack конфигурации, связанной с оптимизацией кода и начнем с того, что просто взглянем на вес production сборок vendor части, ng-cli и нашей.

Сборка ng cli - 100 Кбайт (main.js + polifills.js)

Наша сборка - 357 Кбайт (app.js + vendor.js)

38. Заметная разница, но раз мы используем одинаковые лоадеры, дело тут будет в минификации кода, давайте посмотрим, что Angular CLI использует в качестве оптимизации и скопируем это себе.

optimization: {
    minimize: true,
    minimizer: [
        new JavaScriptOptimizerPlugin({
            advanced: true,
            define: {ngDevMode: false, ngI18nClosureMode: false, ngJitMode: false},
            keepNames: false,
            removeLicenses: true,
            sourcemap: false,
            target: 7
        }),
        new TransferSizePlugin(),
        new CssOptimizerPlugin({
            esbuild: {
                alwaysUseWasm: false,
                initialized: false
            }
        })
    ]

JavaScriptOptimizerPlugin - переопределяют работу стандартного terser-plugin

TransferSizePlugin - записывает вес ассета

CssOptimizerPlugin - убирает пробелы из css

39. На этом моменте вес сборки должен уменьшиться, у меня он сократился до 150 Кбайт.

40. Теперь раздробим наш vendor на отдельные куски с кодом используемых библиотек, добавим следующее в webpack config

optimization: {
    minimize: true,
    runtimeChunk: 'single',
    splitChunks: {
        chunks: "all",
        maxAsyncRequests: Infinity,
        minSize: 0,
        cacheGroups: {
            defaultVendors: {
                test: /[\\/]node_modules[\\/]/,
                name(module) {
                    const name = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
                    return `${name.replace('@', '')}`;
                }
            },
        }
    }
}

41. Теперь в сборочной директории можно увидеть все скрипты, которые мы используем для запуска нашего приложения, запустим dev-server и посмотрим, что все успешно работает.

42. Добавим переменную окружения, чтобы разграничить prod и dev конфигурации, заменим экспорт объекта конфигурации webpack на экспорт функции и в соответствии с этим изменим некоторые поля конфигурации.

module.exports = (env) => {}
mode: env.production ? "production" : "development",
devtool: env.production ? false : "eval",
output: {
    clean: true,
    path: path.resolve(__dirname, "dist"),
    filename: env.production ? "[name].[chunkhash].js" : "[name].js"
},

43. Добавим скрипты запуска в package.json

"start": "webpack serve --env development ",
"build": "webpack --progress --env production",
"build:dev": "webpack --progress"

Отлично, мы сделали все, что нужно, теперь можем разрабатывать наше приложение

Ссылка на репозиторий с кодом

Выводы:

  • Создание такой сборки своими руками - долго, однако при этом вы полностью контролируете процесс и в дальнейшем можно будет просто копировать конфигурацию
  • Контроль над процессом дает понимание того, зачем мы устанавливаем тот или иной пакет
  • Мы потеряли возможность использовать ng update, чтобы обновлять версию angular

Что еще можно сделать с таким приложением:

Можно пойти в ширину и создать таким образом не только SPA, но и отдельную библиотеку или модуль, которые можно будет потом импортировать в приложение. Грубо говоря, создать что-то вроде личного кабинета с lazy-loading и использованием сторонних API, можно также использовать webpack.externals и другие возможности webpack

Что не вошло в данный туториал:

Во время изучения Angular я углубился в сам процесс его работы, узнал как работает Ivy компилятор, что такое AOT режим и на что конкретно он влияет, как обрабатываются шаблоны, что такое ngcc, ngtsc, для чего нужны те или иные библиотеки.

Источники:

Документация по webpack

Документация по typescript

Документация по angular

How Angular works

Deep Dive into the Angular Compiler

Исследование Ivy — нового компилятора Angular

Описание AOT

Использование Ivy Linker

Архитектура движка Ivy



Мне нравится 0   Мне не нравится 0



Comments

Чтобы добавить видео с YouTube, нужно написать [@youtube=xxxxx] , где xxxxx – ID видео.


Комментарии: 0
Нет ни одного комментария.

Новое
Зал короля Артура оказался неолитическим загоном для скота 3 дня назад, 09:05
Зал короля Артура оказался неолитическим загоном для скота
15 действительно вкусных салатов с крабовыми палочками Сб 16.11.2024
15 действительно вкусных салатов с крабовыми палочками
Почему W-образные моторы уходят в прошлое, если они были лучше V-образных Ср 13.11.2024
Почему W-образные моторы уходят в прошлое, если они были лучше V-образных
Когда устал от алгоритмов: Ревью кода на собеседовании Вт 12.11.2024
Когда устал от алгоритмов: Ревью кода на собеседовании
Вирусы на Android: подробное руководство по обеспечению безопасности Пн 11.11.2024
Вирусы на Android: подробное руководство по обеспечению безопасности
Пн 11.11.2024
10 не самых очевидных причин, чтобы уволиться
Искусственный мозг против квантового компьютера: кто возьмет верх? Вс 10.11.2024
Искусственный мозг против квантового компьютера: кто возьмет верх?
10 лучших салатов с кукурузой Сб 09.11.2024
10 лучших салатов с кукурузой
10 вкусных салатов с фасолью, которые хочется готовить снова и снова Сб 02.11.2024
10 вкусных салатов с фасолью, которые хочется готовить снова и снова
Пишем одностраничное приложение с помощью htmx Вт 29.10.2024
Пишем одностраничное приложение с помощью htmx
Books
Blazor in Action Вт 04.06.2024
Blazor in Action
Год: 2022
Security for Containers and Kubernetes Вт 28.05.2024
Security for Containers and Kubernetes
Год: 2023
Designing Data-Intensive Applications Вт 14.05.2024
Designing Data-Intensive Applications
Год: 2017
Fundamentals of Software Architecture Вт 07.05.2024
Fundamentals of Software Architecture
Год: 2020
Разработано на основе BlackNight CMS
Release v.2024-11-16
© 2000–2024 Blackball
Design & programming:
AboutAdvertising
Visitors
Web-site performed by Sergey Drozdov
BlackballAdvertisingStatsПоддержка
MusicPlaylistsCinemaVideoGamesAudioDownloadsMagazinePicturesHumorForumWebsite journalSend contentFeatured