Rollup: você já pode criar aplicativos

O Rollup é um construtor de aplicativos e bibliotecas javascript de nova geração. Muitas pessoas o conhecem há muito tempo como um construtor promissor, adequado para a construção de bibliotecas, mas pouco adequado para a construção de aplicativos. No entanto, com o passar do tempo, o produto está se desenvolvendo ativamente.

Eu tentei pela primeira vez no início de 2017. Imediatamente gostei do suporte à compilação no ES2015, à partilha de árvores, à falta de módulos na montagem e, claro, a uma configuração simples. Mas então era um produto bruto, com um pequeno número de plug-ins e funcionalidade muito limitada, e decidi deixá-lo para mais tarde e continuei a construir através do browserify. A segunda tentativa foi em 2018, quando já superou significativamente a comunidade, plugins e funcionalidade, mas ainda faltava qualidade em algumas funções, incluindo o observador. E, finalmente, no início de 2019, podemos dizer com segurança - com a ajuda do Rollup, você pode criar aplicativos modernos de maneira simples e conveniente.

Para entender os benefícios, vamos examinar os principais recursos e comparar com o Webpack (a situação é a mesma para o Browserify).

Configuração simples


Imediatamente o que chama sua atenção é uma configuração muito simples e compreensível:

export default [{ input: 'src/index.ts', output: [{ file: 'dist/index.min.js', format: 'iife' }], plugins: [ // todo:     ], }]; 

Digite rollup -c no cosnol e seu pacote configurável começará a ser montado. Para exportação, você pode fornecer uma variedade de pacotes configuráveis ​​para montagem, por exemplo, se você coletar polifilos separadamente, vários programas, trabalhadores e assim por diante. Na entrada, você pode enviar uma matriz de arquivos e os pedaços serão coletados. Na saída, você pode alimentar uma matriz de arquivos de saída e montá-los em diferentes sistemas modulares: iife, commonjs, umd.

Suporte Iife


Suporte para montagem na própria função chamada sem módulos. Para entender, vamos dar o programa mais famoso:

 console.log("Hello, world!"); 

conduza-o pelo formato Rollup para iife e veja o resultado:

 (function () { 'use strict'; console.log("Hello, world!"); }()); 

A saída é um código muito compacto, apenas 69 bytes. Se você ainda não entender a vantagem, o Webpack / Browserify compilará o seguinte código:

Resultado de compilação do Webpack
 /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) { console.log("Hello, world!"); /***/ }) /******/ ]); 


Como você pode ver, ficou um pouco mais devido ao fato de o Webpack / Browserify poder ser construído apenas no CommonJS. A grande vantagem do IIFE é sua compactação e a ausência de conflitos entre diferentes versões do CommonJS. Mas há uma desvantagem: não é possível coletar pedaços, pois é necessário mudar para o CommonJS.

Compilação no ES2015


O acúmulo de nome "coletor de próxima geração" em 2016 recebeu a capacidade de montar no ES2015. E até o final de 2018, era o único colecionador que sabia como fazer isso.
Por exemplo, se você pegar o código:

 export class TestA { getData(){return "A"} } console.log("Hello, world!", new TestB().getData()); 

e percorrer o Rollup, depois, na saída, obtemos a mesma coisa. E sim! No início de 2019, já 87% dos navegadores podem executá-lo nativamente.

Então, em 2016, parecia uma inovação, porque havia um grande número de aplicativos que não precisavam de suporte para navegadores antigos: administradores, quiosques, não aplicativos da Web, e não havia ferramentas de construção para eles. E agora, com o Rollup, podemos coletar vários pacotes de uma só vez, em es3, es5, es2015, exnext e, dependendo do navegador, baixe o necessário.

Também uma grande vantagem do ES2015 é seu tamanho e velocidade de execução. Devido à falta de transpilar para uma camada inferior, o código acaba sendo muito mais compacto e, devido à falta de um código auxiliar gerado pelos transpilers, esse código também funciona 3 vezes mais rápido (de acordo com meus testes subjetivos).

Árvore tremendo


Este é um chip Rollup, ele o inventou! O Webpack tenta implementá-lo há muitos anos seguidos, mas somente na versão 4 algo começou a dar certo. Browserify está indo muito mal.
Que tipo de animal é esse? Vamos pegar os dois arquivos a seguir como exemplo:

 // module.ts export class TestA { getData(){return "A"} } export class TestB { getData(){return "B"} } // index.ts import { TestB } from './module'; const test = new TestB(); console.log("Hello, world!", test.getData()); 

execute o Rollup e obtenha:

 (function () { 'use strict'; class TestB { getData() { return "B"; } } const test = new TestB(); console.log("Hello, world!", test.getData()); }()); 

Como resultado do TreeShaking, o código morto foi descartado mesmo no estágio de resolução de dependências. Graças ao que os conjuntos Rollup são muito mais compactos. Agora vamos ver o que o Webpack gera:

Resultado de compilação do Webpack
 /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); // CONCATENATED MODULE: ./src/module.ts class TestA { getData() { return "A"; } } class TestB { getData() { return "B"; } } // CONCATENATED MODULE: ./src/index.ts const test = new TestB(); console.log("Hello, world!", test.getData()); /***/ }) /******/ ]); 


E aqui podemos tirar duas conclusões. O primeiro Webpack no final de 2018 ainda aprendeu a entender e criar o ES2015. O segundo, absolutamente todo o código, cai no assembly, mas o código morto já foi removido usando o minificador Terser (fork e sucessor do UglifyES). Como resultado dessa abordagem, pacotes mais grossos que o Rollup, em um habr já escreveram muito sobre isso, não vamos parar com isso.

Plugins


Somente o ES2015 + nu pode ser montado a partir de uma caixa Rollup. Para lhe ensinar funcionalidades adicionais, como conectar commonjs, módulos de texto datilografado, carregar html e scss, etc., você precisa conectar plugins.

Isso é feito de maneira muito simples:
 import nodeResolve from 'rollup-plugin-node-resolve'; import commonJs from 'rollup-plugin-commonjs'; import typeScript from 'rollup-plugin-typescript2'; import postcss from 'rollup-plugin-postcss'; import html from 'rollup-plugin-html'; import visualizer from 'rollup-plugin-visualizer'; import {sizeSnapshot} from "rollup-plugin-size-snapshot"; import {terser} from 'rollup-plugin-terser'; export default [{ input: 'src/index.ts', output: [{ file: 'dist/index.r.min.js', format: 'iife' }], plugins: [ nodeResolve(), //   node commonJs(), //   commonjs postcss(), //   postcc,    scss  less html(), //  html  typeScript({tsconfig: "tsconfig.json"}), //  typescript sizeSnapshot(), //      terser(), //    ES2015+,    UglifyES visualizer() //   ] }]; 

Tão simples, um plug-in está conectado em uma palavra. Essa configuração pode coletar qualquer aplicativo complexo e até gerar uma análise de pacote na saída.

Sumário


E agora, munidos de novos conhecimentos, vamos fazer uma configuração que coletará polifilias separadamente, um aplicativo separado para navegadores antigos e novos, um trabalhador de serviço para pwa e um trabalhador da web para cálculos complexos.

 import nodeResolve from 'rollup-plugin-node-resolve'; import commonJs from 'rollup-plugin-commonjs'; import typeScript from 'rollup-plugin-typescript2'; import postcss from 'rollup-plugin-postcss'; import html from 'rollup-plugin-html'; import visualizer from 'rollup-plugin-visualizer'; import { sizeSnapshot } from "rollup-plugin-size-snapshot"; import { terser } from 'rollup-plugin-terser'; const getPlugins = (options) => [ nodeResolve(), commonJs(), postcss(), html(), typeScript({ tsconfig: "tsconfig.json", tsconfigOverride: { compilerOptions: { "target": options.target } } }), sizeSnapshot(), terser(), visualizer() ]; export default [{ input: 'src/polyfills.ts', output: [{ file: 'dist/polyfills.min.js', format: 'iife' }], plugins: getPlugins({ target: "es5" }) },{ input: 'src/index.ts', output: [{ file: 'dist/index.next.min.js', format: 'iife' }], plugins: getPlugins({ target: "esnext" }) },{ input: 'src/index.ts', output: [{ file: 'dist/index.es5.min.js', format: 'iife' }], plugins: getPlugins({ target: "es5" }) },{ input: 'src/index.ts', output: [{ file: 'dist/index.es3.min.js', format: 'iife' }], plugins: getPlugins({ target: "es3" }) },{ input: 'src/serviceworker.ts', output: [{ file: 'dist/serviceworker.min.js', format: 'iife' }], plugins: getPlugins({ target: "es5" }) },{ input: 'src/webworker.ts', output: [{ file: 'dist/webworker.min.js', format: 'iife' }], plugins: getPlugins({ target: "es5" }) }]; 


Todos os pacotes fáceis e aplicativos da Web rápidos!

Source: https://habr.com/ru/post/pt440946/


All Articles