Обновляем Angular до 6-ой версии в проекте без использования CLI

    В этой статье я расскажу о тернистом пути обновления Angular с кастомным Webpack конфигом, который нашей команде пришлось пройти неделю назад. Возможно, наш опыт будет полезен тем, кто использует Angular со своим Webpack конфигом, а остальным — интересен как иллюстрация того, куда может завести современный frontend и как с этим жить.



    Наша команда работает над интерфейсом BILLmanager 6. Дабы вы имели общее представление о проекте до обновления, сообщу, что количество файлов в нем уже перевалило за 67 тысяч. Архитектурно можно выделить два подпроекта: модуль регистрации и основной пользовательский интерфейс. По технологиям основу составляют компоненты, директивы и модули Angular, написанные на TypeScript. Есть несколько компонентов на Web components. Для стилизации мы используем SASS/SCSS и применяем CSS variables, чтобы темизировать приложение без перекомпиляции.

    Предпосылки


    У всего есть причины, и наши текущие трудности получили свое начало полтора года назад. Тогда только появилась beta Angular 2. У программистов в компании был опыт создания приложений на Angular 1, ReactJS и собственном небольшом фреймворке. Angular 2 на тот момент вобрал в себя плюсы из первой версии и ReactJS. Поэтому и был выбран в силу своей перспективности (как никак Google), успешности Angular 1 и формализации, которую дает TypeScript. Мы не пишем небольшие SPA сайты, которые можно отдать заказчику и забыть, наши приложения живут долго и им нужна постоянная поддержка и развитие. BILLmanager используют провайдеры для продажи хостинга и работы с клиентами. Поэтому и его, и другие продукты ISPsystem, нужно постоянно поддерживать и развивать. В принципе, команда Angular 2 уже тогда везде писала, что теперь будет просто Angular и развитие фреймворка будет происходить эволюционно, что подходит для наших внутренних процессов.

    Как я уже писал, проекты у нас большие и долгоживущие. У них сложные конфиги с гибкими настройками под отдельные сборки. А Webpack давно является своего рода стандартом для сборки больших и маленьких проектов в мире frontend, поэтому здесь выбор был однозначным.

    В итоге общая часть конфига в проекте выглядела следующий образом:

    содержимое файла webpack.config.common.js перед обновлением
    module.exports = {
      context: PATHS.root,
      target: 'web',
      entry,
      resolve: {
        extensions: ['.ts', '.js', '.json'],
        modules: [PATHS.src, PATHS.node_modules],
      },
      module: {
        rules: [{
            test: /\.ts$/,
            loaders: [{
                loader: 'awesome-typescript-loader',
                options: {
                  transpileOnly: process.env.NODE_ENV !== 'production'
                }
              },
              'angular2-template-loader',
              'angular2-router-loader'
    
            ],
            exclude: [/\.(spec|e2e)\.ts$/],
          },
          {
            test: /\.ts$/,
            include: [/\.(spec|e2e)\.ts$/],
            loaders: ['awesome-typescript-loader', 'angular2-template-loader']
          },
          {
            test: /\.json$/,
            use: 'json-loader'
          },
          {
            test: /\.html$/,
            use: [{
              loader: 'html-loader',
            }],
          },
          {
            test: /\.(eot|woff|woff2|ttf|png|jpg|gif|svg|ico)(\?v=\d+\.\d+\.\d+)?$/,
            loader: 'file-loader',
            options: {
              context: PATHS.assets,
              name: '[path][name].[ext]'
            },
          },
          {
            test: /\.css$/,
            loader: extractSASS.extract({
              fallback: 'style-loader',
              use: 'css-loader?sourceMap'
            }),
            exclude: [path.join(PATHS.projectPath), path.join(PATHS.src, 'common'), path.join(PATHS.src, 'common-bill')],
          },
          {
            test: /\.css$/,
            include: [path.join(PATHS.projectPath), path.join(PATHS.src, 'common'), path.join(PATHS.src, 'common-bill')],
            use: [{
              loader: "raw-loader" // creates style nodes from JS strings
            }],
          },
          {
            test: /\.(scss|sass)$/,
            loader: extractSASS.extract({
              use: [{
                  loader: "css-loader",
                },
                {
                  loader: "sass-loader",
                  options: {
                    sourceMap: true,
                  }
                }
              ],
              // use style-loader in development
              fallback: "style-loader"
            }),
            exclude: [path.join(PATHS.projectPath), path.join(PATHS.src, 'common'), path.join(PATHS.src, 'common-bill')],
          },
          {
            test: /\.(scss|sass)$/,
            use: [{
                loader: "raw-loader" // creates style nodes from JS strings
              },
              {
                loader: "sass-loader", // compiles Sass to CSS
                options: {
                  sourceMap: true
                }
              }
            ],
            include: [path.join(PATHS.projectPath), path.join(PATHS.src, 'common'), path.join(PATHS.src, 'common-bill')],
          }
        ]
      },
      plugins: [
        extractSASS,
        new webpack.IgnorePlugin(/vertx/),
        new webpack.ContextReplacementPlugin(
          // The (\\|\/) piece accounts for path separators in *nix and Windows
          /\@angular(\\|\/)core(\\|\/)esm5/,
          PATHS.projectPath, // location of your src
          {} // a map of your routes
        ),
        new webpack.optimize.CommonsChunkPlugin({
          name: ['vendor', 'polyfills'],
          // minChunks: Infinity
        }),
      ]
    };
    


    Это очень похоже на то, что описано сейчас в документации Angular angular.io/guide/webpack. Наиболее интересной из этого является часть про компиляцию .ts файлов.
          
    {
      test: /\.ts$/,
      loaders: [{
          loader: 'awesome-typescript-loader',
          options: {
            transpileOnly: process.env.NODE_ENV !== 'production'
          }
        },
        'angular2-template-loader',
        'angular2-router-loader'
    
      ],
      exclude: [/\.(spec|e2e)\.ts$/],
    },
    {
      test: /\.ts$/,
      include: [/\.(spec|e2e)\.ts$/],
      loaders: ['awesome-typescript-loader', 'angular2-template-loader']
    },
    

    Как видите, мы используем лоадер angular2-template-loader и angular2-router-loader для сборки наших компонентов Angular. В официальной документации так и написано. И это крайне странно, так как оба лоадера написаны не командой Angular и лежат в пользовательских репозиториях на GitHub. Одной из причин выбора Angular в качестве основного фреймворка было как раз то, что он работает как комбайн — всё идет «из коробки», в отличие от того же ReactJS. Но тут мы видим, что инструмент, которым будет собираться наш проект, «из коробки» не идет.

    Ну да ладно, такой конфиг работал со второй по пятую версию, и причин для беспокойства не было. Хотя нет, была одна. На ng-conf 2017 Brad Green рассказал о попытке сборки Angular приложения с помощью Bazel и Closure. Кто работал с большими проектами на Angular, тот поймет меня — сборки проходят очень долго. Наша первая сборка development режима на пятой версии Angular со вторым webpack занимает больше 4 минут. И стремление разработчиков фреймворка сделать сборки быстрее вполне обосновано. Хотя существует и другой взгляд на эту ситуацию. Как сказал мой коллега:
    «Надо же было сделать медленно собирающийся фреймворк, а потом начать его ускорять».

    Обновление Angular до 6-ой версии


    В нашей компании хорошо понимают, что происходит, когда не обновляешь инструменты. Мы знаем, к чему это может в итоге привести. Революции не проходят безболезненно, и лучше их предвидеть, двигаясь эволюционным путем. Поэтому при появлении новости о выходе стабильной версии Angular 6, мы решили обновить наш проект. Но безболезненно сделать это не получилось.

    Как и при обновлении предыдущей версии, мы перешли на сайт с руководством по обновлению update.angular.io. Здесь нас ждал первый сюрприз.



    Если не указать пункт “I use ngUpgrade”, то руководство всё равно предложит выполнить команду ng update @angular/core.



    В обновлении предыдущих версий такого не было, а теперь, получается, без CLI и обновиться-то нельзя? Возможно, это баг и он будет исправлен, но сегодня, как и неделю назад, получить четких инструкций по обновлению без CLI не удалось.

    Если как и мы, вы все же хотите продолжить обновлять проект, то именно тут начинается наш тернистый путь. Для начала нужно определиться с направлением:

    1. Установить CLI и обновлять по шагам официального руководства.
    2. Обновлять пакеты отдельно и редактировать конфиги самостоятельно.

    Первый показался нам проще и мы пошли по нему.
    Но после установки, обновления CLI и выполнения команды ng update @angular/core нас ожидало разочарование.

    $ ng update @angular/core
    Package "@angular/compiler-cli" has an incompatible peer dependency to "typescript" (requires ">=2.7.2 <2.8", would install "2.8.3")
    Invalid range: ">=2.3.0 <3.0.0||>=4.0.0"

    В issues на GitHub можно найти github.com/angular/angular-cli/issues/10621. На сегодняшний день эту ошибку вроде бы поправили (судя по github.com/angular/devkit/pull/901), но на тот момент мы решили не лезть в дебри утилиты обновления и обновили пакеты вручную.

    После обновления пакетов проект перестал запускаться, что, собственно, было ожидаемо. Angular 6 использует Webpack 4 (это можно увидеть, если вы поставите его через CLI). Поэтому на следующем шаге мы обновили Webpack и смежные пакеты до последних версий. Рассказ про обновление Webpack тянет на отдельную статью, поэтому здесь напишу только, что если вы используете extract-text-webpack-plugin, замените его на mini-css-extract-plugin, и это сэкономит вам нервы и силы. Почитать про то, как хорош четвертый webpack, можно здесь, ну и, собственно, статья по миграции.

    Помимо обновления Angular и Webpack, нужно обновить RxJS до шестой версии, иначе проект просто не запустится. Это обязательное условие и выполнить его несложно, нужно просто следовать документации по миграции. Существенных трудностей возникнуть не должно, RxJS предоставляет утилиту, которая самостоятельно вносит необходимые изменения в проект.

    Тем временем мы возвращаемся к обновлению до Angular 6. Проект по-прежнему не собирается и выдает массу невразумительных ошибок. Здесь самое время обратить внимание на лоадер, которым обрабатываются .ts файлы. У нас используется связка angular2-template-loader и angular2-router-loader. Если зайти в репозиторий angular2-template-loader, то видно, что он не обновлялся уже как год (странно, что в официальной документации нам все еще предлагают использовать его).



    Похоже, что проблема в том как этот лоадер обрабатывает наш код. Мы начали искать замену и нашли плагин для Ahead-of-Time (AoT) компиляции @ngtools/webpack. Это не равнозначная замена, так как до этого нами использовалась только JIT-компиляция. Но, с другой стороны, команда Angular уже давно говорит о планах сделать AoT-компиляцию по умолчанию. @ngtools/webpack — официальный инструмент из Angular DevKit, он обновляется постоянно и был переработан под шестую версию фреймворка. Для справедливости замечу, что можно собрать Angular 6 проект и с плагинами angular2-template-loader и angular2-router-loader. Связка этих плагинов может подойти для разработки, но для production сборок лучше их не использовать из-за отсутствия дополнительных проверок исполняемого кода. Именно это не позволило нам легко сразу же отловить все необходимые исправления для перехода на шестую версию.

    AOT-компиляция по большому счету отличается тем, что шаблоны компилируются в момент сборки приложения, а не после запуска в браузере пользователя. С одной стороны, это ускоряет работу приложения и позволяет сократить его размер, с другой — несет дополнительные затраты при компиляции и требует больше строгости к написанию компонентов.

    Переход на AOT-компиляцию проекта — это отдельная большая задача. Для ее выполнения придется переделать большую часть проекта, потому что AOT предъявляет очень строгие требования к коду и если сразу вы их не соблюдали, то будет сложно. Но есть выход. Можно использовать плагин @ngtools/webpack с JIT-компиляцией. Для этого нужно добавить параметр skipCodeGeneration=true в настройки плагина.

    Обозначу основные моменты, которые пришлось исправить при переходе на @ngtools/webpack плагин:

    1. В шаблонах все private переменные заменены на public.
    2. При наследовании нежелательно наследовать один компонент от другого (с директивами то же самое). В принципе, это логично, но angular2-template-loader — пропускал, а @ngtools/webpack стал ругаться на неправильно созданные модули.
    3. Если пренебречь рекомендацией выше, то можно получить ошибку при использовании в конструкторе компонента переменных простых типов. Это наиболее странная ошибка. Компонент выглядит следующим образом:

    @Component({
     selector: '[form-component]',
     template: ''
    })
    export class FormComponent extends BaseComponent implements OnInit {
     constructor(
       public formService: FormService,
       public formFunc: string,
       public formParams: Array<any> = []
     ) {
       super();
     }
    ...
    

    В логах видим примерно следующее:

    ERROR in : Can't resolve all parameters for FormComponent in form.component.ts: ( [object Object], ?, ?)

    Рекомендую все же выполнить правило второе, но если по каким-либо причинам это не получается, вы можете сделать небольшой хак https://stackoverflow.com/a/48748942/4778628, заменив код выше на:

    @Component({
     selector: '[form-component]',
     template: ''
    })
    export class FormComponent extends BaseComponent implements OnInit {
     constructor(
       public formService: FormService,
       @Inject('') public formFunc: string,
       @Inject('') public formParams: Array<any> = []
     ) {
       super();
     }
    ...
    

    К сожалению, на этом ошибки не закончились, и мы получили её в самом плагине компилятора Angular:

    текст ошибки
     [0] building modules 「wds」: Project is running at http://localhost:8080/
     「wds」: webpack output is served from /
     「wds」: Content not from webpack is served from /home/dsumbaev/DEVELOPMENT/bill-client-front/dist
     「wds」: 404s will fallback to /index.html
    [0] building modules/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:509
                        if (this.done && (request.request.endsWith('.ts')
                                                  ^
    
    TypeError: Cannot read property 'request' of null
        at nmf.hooks.beforeResolve.tapAsync (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:509:47)
        at _fn1 (eval at create (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/node_modules/tapable/lib/HookCodeFactory.js:24:12), <anonymous>:27:1)
        at Object.resolveWithPaths (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/@ngtools/webpack/src/paths-plugin.js:14:9)
        at nmf.hooks.beforeResolve.tapAsync (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/@ngtools/webpack/src/angular_compiler_plugin.js:521:32)
        at AsyncSeriesWaterfallHook.eval [as callAsync] (eval at create (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/node_modules/tapable/lib/HookCodeFactory.js:24:12), <anonymous>:19:1)
        at NormalModuleFactory.create (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/lib/NormalModuleFactory.js:338:28)
        at semaphore.acquire (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/lib/Compilation.js:494:14)
        at Semaphore.acquire (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/lib/util/Semaphore.js:17:4)
        at asyncLib.forEach (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/lib/Compilation.js:492:15)
        at arrayEach (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/neo-async/async.js:2400:9)
        at Object.each (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/neo-async/async.js:2835:9)
        at Compilation.addModuleDependencies (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/lib/Compilation.js:471:12)
        at Compilation.processModuleDependencies (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/lib/Compilation.js:450:8)
        at afterBuild (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/lib/Compilation.js:556:15)
        at buildModule.err (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/lib/Compilation.js:600:11)
        at callback (/home/dsumbaev/DEVELOPMENT/bill-client-front/node_modules/webpack/lib/Compilation.js:358:35)
    


    Сначала подумали, что отдаем компилятору пакеты из node_modules, а он не может их обработать, но добавление исключений никак не отразилось на ошибке. Деваться было некуда и поворачивать поздно, поэтому появился небольшой PR в @ngtools/webpack. Эти изменения вошли в версию пакета 6.0.1. После этого сборка прошла успешно и проект запустился!

    НО! Оказалось, что не подтянулись все модули кроме основного. Давайте посмотрим на настройку @ngtools/webpack плагина.

       new AngularCompilerPlugin({
         platform: 0,
         sourceMap: true,
         tsConfigPath: path.join(PATHS.root, 'tsconfig.json'),
         skipCodeGeneration: true,
       })

    На первый взгляд, все как в документации. Обратите внимание, что параметр entryModule помечен как необязательный. Методом проб и ошибок выяснили, что если параметр не был указан, то сборка не проходила дальше главного модуля, поэтому мы и получали нерабочее приложение. Исправить проблему легко, нужно добавить entryModule.

       new AngularCompilerPlugin({
         platform: 0,
         entryModule: path.join(PATHS.src, 'apps/client/app/app.module#AppModule'),
         sourceMap: true,
         tsConfigPath: path.join(PATHS.root, 'tsconfig.json'),
         skipCodeGeneration: true,
       })

    Если помните, в начале я писал, что в проекте у нас два подпроекта, но в entryModule можно указать только один. Здесь нам повезло, так как второе приложение не содержит вложенных модулей. Если же у вас другая ситуация: несколько сложных проектов внутри одного, то вам придется сделать для каждого отдельные конфиги или дождаться прохождения этого PR в репозитории Angular DevKit.

    В результате общая часть конфига в проекте стала следующей:

    итоговое содержимое файла webpack.config.common.js
    const path = require('path');
    const merge = require('webpack-merge');
    const webpack = require('webpack');
    const ProgressPlugin = require('webpack/lib/ProgressPlugin');
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const {AngularCompilerPlugin} = require('@ngtools/webpack');
    
    const {
     PATHS,
     PARAMS
    } = require('./helpers.js');
    const devMode = process.env.NODE_ENV === 'development';
    
    let entry = {
     'polyfills': path.join(PATHS.src, 'polyfills.browser.ts'),
     'main': path.join(PATHS.projectPath, 'main.ts'),
     'extform': path.join(PATHS.apps, 'extform/main.ts'),
     'style': path.join(PATHS.assets, 'sass', 'app.sass')
    };
    
    PARAMS.themes.forEach(theme => {
     entry['themes/' + theme + '/theme'] = path.join(PATHS.themes, theme, 'theme.scss')
    });
    
    module.exports = {
     context: PATHS.root,
     target: 'web',
     entry,
     resolve: {
       extensions: ['.ts', '.js', '.json'],
       modules: [PATHS.src, PATHS.node_modules],
     },
     mode: process.env.NODE_ENV,
     stats: 'errors-only',
     module: {
       rules: [{
           test: /\.ts$/,
           loader: '@ngtools/webpack',
           exclude: [/\.(spec|e2e)\.ts$/, /node_modules/],
         },
         {
           test: /\.ts$/,
           loader: 'null-loader',
           include: [/\.(spec|e2e)\.ts$/],
         },
         {
           test: /\.json$/,
           use: 'json-loader'
         },
         {
           test: /\.html$/,
           use: [{
             loader: 'html-loader',
           }],
         },
         {
           test: /\.(eot|woff|woff2|ttf|png|jpg|gif|svg|ico)(\?v=\d+\.\d+\.\d+)?$/,
           loader: 'file-loader',
           options: {
             context: PATHS.assets,
             name: '[path][name].[ext]'
           },
         },
         {
           test: /\.css$/,
           use: [
             MiniCssExtractPlugin.loader,
             "css-loader"
           ],
           exclude: [path.join(PATHS.projectPath), path.join(PATHS.src, 'common'), path.join(PATHS.src, 'common-bill')],
         },
         {
           test: /\.css$/,
           include: [path.join(PATHS.projectPath), path.join(PATHS.src, 'common'), path.join(PATHS.src, 'common-bill')],
           use: [{
             loader: "raw-loader" // creates style nodes from JS strings
           }],
         },
         {
           test: /\.(scss|sass)$/,
           use: [
             devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
             'css-loader',
             'sass-loader',
           ],
           exclude: [path.join(PATHS.projectPath), path.join(PATHS.src, 'common'), path.join(PATHS.src, 'common-bill')],
         },
         {
           test: /\.(scss|sass)$/,
           use: [{
               loader: "raw-loader" // creates style nodes from JS strings
             },
             {
               loader: "sass-loader", // compiles Sass to CSS
               options: {
                 sourceMap: true
               }
             }
           ],
           include: [path.join(PATHS.projectPath), path.join(PATHS.src, 'common'), path.join(PATHS.src, 'common-bill')],
         }
       ]
     },
     optimization: {
       splitChunks: {
         cacheGroups: {
           commons: {
             test: /[\\/]node_modules[\\/]/,
             name: "vendors",
             chunks: "all"
           }
         }
       }
     },
     plugins: [
       new MiniCssExtractPlugin({
         filename: '[name].[hash].css',
       }),
       new webpack.IgnorePlugin(/vertx/),
       new ProgressPlugin(),
       new AngularCompilerPlugin({
         platform: 0,
         entryModule: path.join(PATHS.src, 'apps/client/app/app.module#AppModule'),
         sourceMap: true,
         tsConfigPath: path.join(PATHS.root, 'tsconfig.json'),
         skipCodeGeneration: true,
       })
     ]
    };
    


    Заключение


    После всех вышеописанных манипуляций мы получили работающее приложение с Angular 6 и собственным Webpack конфигом. В ходе работ было скорректировано 180 файлов проекта, и заняло это около одной недели.

    Angular — монолитный фреймворк, и с приходом шестой версии это становится видно еще больше. Теперь не только дополнительные инструменты в виде роутера или библиотеки HTTP-запросов, но и инструменты сборки идут «из коробки». И лучше их не трогать, не вносить изменения, которые не были задуманы разработчиками Angular. Только в этом случае вы сможете легко обновлять проект и, возможно, не будете сталкиваться с необходимостью менять сотни файлов после обновлений. Иначе, вас ожидает нелегкий путь и придется лайкать негативные отзывы по новой версии в море позитива и общей радости окружающих.

    Это не значит, что Angular плохой или хороший, просто он требует особого обращения и подойдет не всем. Если вы будете работать с ним через CLI, собирать проект ng утилитой, ей же тестировать и создавать модули и компоненты, то будет вам счастье. В своей команде мы бы тоже хотели так, но, увы и ах, слишком много уже завязано на наш конфиг Webpack. Как говорится в хорошей русской пословице: «Знал бы, где упадешь, — соломки бы подстелил». Еще год назад CLI не был столь обязательным инструментом в Angular проектах, ну а сегодня даже в документации по обновлению нет руководства как обновлять без него.
    ISPsystem 143,69
    Софт для хостинга: ISPmanager, BILLmanager и др.
    Поделиться публикацией
    Комментарии 22
    • 0
      А какие такие изменения, привнесенные в 6-ю версию, заставили вас на него перейти?
      Мы вот до сих пор на 4.4 сидим и вроде всё устраивает…
      • +2
        установка пакетов через ng add, которая без «открой X, вставить туда строчку Y»
        ng update, который вас практически избавит от ручного обновления пакетов
        RxJS 6.0 и улучшенный tree-shaking
        Анимации без полифоллов
        Компоненты без ангуляра (например, чтоб шерить их между основным сайтом и лендингом на WP)

        это как минимум
        • 0
          Компоненты без ангуляра (например, чтоб шерить их между основным сайтом и лендингом на WP)

          А можно об этом поподробнее? Или ссылку скиньте, плз.

          • +1
            Там не совсем без ангуляра, там идея упаковки компонента со всеми его зависимостями в нативный web component.

            Я говорю об Angular Elements, angular.io/guide/elements.
            Оно ещё очень сырое, но нас подходит, мы начнем готовить некоторые элементы как раз под описанный выше случай.
            Более-менее production-ready это всё будет уже к 7 релизу.
        • +1
          Основных мотиваций для обновления было две:
          1. Хотелось ускорить сборку проекта. Уже давно следим за новостями по Webpack 4. В этом видео Sean Larkin рассказывает подробнее. Angular 6 по-умолчанию использует Webpack 4 поэтому решили обновлять совместно. В итоге сейчас проект стал собираться на 40 секунд быстрее (думаю можно будет еще ускорить, но уже приятно).
          2. С каждым обновлением команда Angular вносит ряд изменений в фреймворк, которые требуют переработки компонентов. Если затягивать с обновлением, то потом придется менять слишком много кода. Даже в update.angular.io если указать переход через несколько мажорных версии они напишут предупреждение:
          Warning: We do not recommend moving across multiple major versions.
          • 0
            Angular 6 по-умолчанию использует Webpack 4

            Angular CLI, самому Angular Webpack без надобности.
            • 0
              Говоря Angular 6 я подразумеваю экосистему Angular — это и rxjs внутри него и CLI и все остальное необходимое для получения конечного приложения, способного запуститься в браузере пользователя. Если рассматривать экосистему Angular 6 то по-умолчанию (здесь можно трактовать по-умолчанию=Angular CLI) в ней используется Webpack 4. Хотя, конечно, Angular 6 можно попробовать собрать Webpack 3 и даже rollup.js :)
            • 0

              Если не используете какие-то специфичные фичи angular-cli, то может быть выгоднее самим написать сборку, а вместо uglify (которым и жмет вебпак) можно использовать google closure compiler, многопоточный, на джаве.

              • 0
                Наш первый конфиг в общем то это и делал, но это опасный путь) Есть высокая степень вероятности, что при очередном обновлении проект не запустится т.к. разработчики Angular добавили новый «аспект», который учитывается в @ngtools/webpack а вот в ts-loader или awesome-typescript-loader его ещё нужно правильно обработать. По поводу uglify, знаю, что внутри CLI при сборке он тоже используется, про google closure compiler ничего сказать не могу, не использовал.
            • +1
              А как вы сидите на 4.4 если плагины собранные под Angular 5.x+ не работают на 4.x? Не говоря уже о breaking changes. Или вы не используете сторонние пакеты? Или намеренно используете устаревшие и не поддерживаемые версии?
              • 0
                «dependencies»: {
                "@angular/common": «4.4.6»,
                "@angular/compiler": «4.4.6»,
                "@angular/core": «4.4.6»,
                "@angular/forms": «4.4.6»,
                "@angular/http": «4.4.6»,
                "@angular/platform-browser": «4.4.6»,
                "@angular/platform-browser-dynamic": «4.4.6»,
                "@angular/animations": «4.4.6»,
                "@angular/router": «4.4.6»,
                "@angular/compiler-cli": «4.4.6»,
                «angular-in-memory-web-api»: «0.5.1»,
                «bootstrap»: «3.3.7»,
                «core-js»: «2.5.1»,
                «eonasdan-bootstrap-datetimepicker»: «4.17.47»,
                «font-awesome»: «4.7.0»,
                «ie-shim»: «0.1.0»,
                «moment»: «2.15.1»,
                «reflect-metadata»: "^0.1.10",
                «rxjs»: «5.5.2»,
                «zone.js»: «0.8.18»,
                «aspnet-prerendering»: «2.0.3»,
                «aspnet-webpack»: «2.0.1»,
                «file-saver»: «1.3.3»,
                «d3»: «4.7.4»
                },
            • +2

              Очень интересно, как у вас работал старый конфиг вебпака вот с этой строкой


              transpileOnly: !process.env.NODE_ENV === 'production'

              Порядок операций такой, что это эквивалентно: false === 'production', то есть всегда false.


              Радует, что в финальной версии конфига этой строки уже нет :)

              • 0
                Да, точно, спасибо поправил :)
                Это в ходе экспериментов так выключал/включал дополнительные проверки в попытках ускорить сборку, а обратно для статьи поменять забыл.
              • 0
                @ngtools/webpack работает и с Webpack 3, в моём случае приложение, обновлённое до Angular 6, прекрасно собиралось с Webpack 3. А из-за того что изначально использовался @ngtools/webpack (ещё с беты), каких-то проблем по переходу с Angular 5 на Angular 6 я не увидел.

                А вот новый SplitChunksPlugin пришлось осознавать довольно долго.
                • 0
                  каких-то проблем по переходу с Angular 5 на Angular 6 я не увидел.

                  Потому что проблем нет, тоже без CLI обновлялся, по сути только небольшиме изменения связанные RxJS потребовались и они выполнялись автоматически скриптом предоставленным самой же RxJS командой.
                • +1

                  Спасибо за пост. Сам хотел нечто подобное описать, т.к. был очень схожий болезненный опыт перехода, но на 5ую версию. Проект также работал без CLI и был довольно увесистый.


                  Мы решили, что наиболее правильным решением будет перейти на CLI, и несмотря на огромное количество убитого времени (неделя боли и страданий) проект стал работать через CLI и даже с AOT компиляцией. Правда это стало последним каплей и следующие проекты мы делали уже на другом фреймворке...

                  • 0
                    Неделя времени не так и много учитывая что у CLI много преимуществ в случае работы в команде и над долгосрочным проектом. Хотя он и кривоватый местами, и не дает возможность пост-конфигурирования вебпак конфига.
                  • 0
                    В свое время просто сделал eject конфига из Angular CLI, выбросил то что не нужно было и таким образом без CLI живу с 4й версии. С выходом новой версии повторял процесс, это муторно на самом деле, но зато понятнее что они там понаделали с вебпаком. Но это для простого опенсорс проекта, в случае работы в команде и в компании выбрал бы CLI конечно.
                    • 0
                      А что конкретно это вам дает кроме «вебпак без того что не нужно»?
                      • 0
                        Простота вебпак конфига для проекта который не требует много фарша.
                        • 0
                          Но пользоваться CLI принципиально проще чем иметь дело с конфигом. И именно фарша будет 0 (т.к. вебпак конфига нет)
                          • 0
                            В некоторых случаях нужна возможность влиять на процесс сборки (кастомизировать вебпак конфиг). Кроме этого CLI тянет много зависимостей не всегда нужных и навязывает некоторые вещи не всегда удобные. Но по хорошему лучше изначально все так проектировать чтобы CLI было достаточно.

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

                    Самое читаемое