AdoptOS

Assistance with Open Source adoption

Open Source News

Melhor portal para funcionários

Liferay - Fri, 06/15/2018 - 17:29
A Criação do Melhor Portal Começa pela Empatia

Se sua empresa está passando por uma transformação digital, contar com um portal de funcionários para ajudar a gerenciar as mudanças culturais é extremamente importante. Muitos dos principais desafios da transformação digital têm a ver com o gerenciamento de mudanças e o fortalecimento de seus funcionários, duas coisas que portais para funcionários bem projetados podem suportar.

A tendência atual de portais para funcionários é geralmente chamada de intranet social ou intranet 2.0. Em vez de sites com informações estáticas, os melhores exemplos de hoje incorporam recursos sociais, como blogs, mensagens instantâneas e comentários. Essa mudança tem sido frequentemente creditada ao novo público de millennials, que está acostumado a ter essas novas formas de comunicação disponíveis em todos os momentos.

Para criar soluções que esse público-alvo realmente use, as equipes de TI devem tratar os funcionários como clientes ao projetar uma nova intranet ou  portal para funcionário. A experiência do cliente é tão importante aqui quanto no site público. A equipe de desenvolvimento precisa ficar aberta para feedbacks e melhorias. A recompensa por esse trabalho será evidente na melhoria da retenção de funcionários, que pode ser vista como a medida definitiva para o sucesso do portal do funcionário.

Seis Perguntas para Fazer Antes de Começar

Há duas coisas que devem ser levadas em consideração em qualquer portal para funcionários desde o início do projeto.

A primeira é a acessibilidade, o que significa que deve ser fácil de usar para que as pessoas não fiquem confusas tentando realizar seu trabalho.

A segunda é o conteúdo relevante, de modo que o portal tenha material suficiente por conta própria, para que as pessoas realmente o usem. Idealmente, o site ganharia impulso suficiente para que os funcionários contribuíssem regularmente com conteúdo e usassem suas ferramentas por iniciativa própria.

Para abordar essas duas áreas, aqui estão seis perguntas que você pode fazer para começar a criar um portal para funcionários que as pessoas realmente usarão:

# 1 Por que Precisamos de um Portal para Funcionários?

Se você não tem uma visão sólida para o seu portal, o processo de design rapidamente perderá o foco. As empresas costumam citar as seguintes razões para a criação de um portal para funcionários ou intranet:

  • Aumento da produtividade
  • Comunicação corporativa unificada
  • Processos de negócios simplificados
  • Gerenciamento de conhecimento e colaboração mais fáceis
  • Integração digital e treinamento
# 2 Qual é o ROI?

O retorno sobre Investimento (ROI) mais claro é muitas vezes a eliminação de processos ineficientes, como funcionários gastando tempo procurando documentos em vários repositórios. A definição das melhores oportunidades para o ROI ajudará você a priorizar o desenvolvimento durante os estágios posteriores do projeto.

# 3 Quando Olhamos para as Nossas Ferramentas Atuais, o que Não Funciona como Deveria?

Esta questão começa a desenvolver  empatia com seus usuários finais. Por exemplo, a maioria das empresas tem pelo menos um processo que depende de alguns funcionários inserindo dados manualmente em uma planilha do Excel. Esse sistema pode funcionar para uma pequena quantidade de dados, mas não é escalável à medida que a empresa cresce. Ou você pode descobrir que os funcionários nunca usam o recurso de pesquisa em sua intranet atual porque não retornam os resultados corretos.

É aqui que você inicia seu plano de design. Ao consertar um processo frustrante, você pode ganhar credibilidade e conquistar seu público no início do projeto.

# 4 O que Nossos Funcionários Precisam Fazer?

As pessoas não gostam de mudanças, mesmo que você prometa que o novo portal que você está construindo facilitará a vida delas depois que elas se acostumarem. O primeiro lançamento do portal para funcionários deve ter as tarefas que as pessoas precisam fazer - registrar quadros de horários, solicitar férias - e colocar essas funções completamente dentro da nova solução, para que os funcionários tenham que usá-la. Isso pode parecer agressivo, mas deve-se atribuir a responsabilidade àequipe de TI por criar um portal que seja realmente eficiente, confiável e fácil de navegar. Apesar da maioria dos conselhos para intranets que você encontra online, projetar o melhor portal para funcionários não é fazer algo bonito. É sobre fazer algo prático. Isso, se bem feito, impulsiona o uso e o engajamento.

# 5 De quais Ferramentas Nossos Funcionários Gostam?

Projetar sua experiência do usuário baseadas em ferramentas que sua equipe já gosta pode ajudar a criar um envolvimento antecipado, porque a experiência do portal parecerá familiar. Se seus funcionários adoram usar as páginas do Facebook para planejar eventos da empresa, crie uma plataforma social interna que inclua esses mesmos recursos.

# 6 Como Nossos Funcionários Colaboram?

É difícil obter a colaboração eficaz através de um portal, mas ele pode trazer alguns dos maiores ganhos em aumentar a produtividade e facilitar a comunicação. Se você puder obter uma compreensão profunda dos pontos problemáticos em torno da colaboração, você se posicionará para desenvolver um portal com ROI significativo. Reserve um tempo pesquisando como os usuários realizam um projeto do início ao fim, anotando todas as ferramentas que usam no processo, desde notas adesivas até quadros de comunicações a mensagens de texto.

Os Melhores Portais de Funcionários Pagam por si Mesmos

Finalmente, o melhor portal para funcionários é aquele que necessita de  menos recursos para gerenciar, ao mesmo tempo que possui o maior impacto na eficiência e no engajamento. Sua solução deve ser algo que os funcionários se sintam confortáveis usando e deve haver adesão suficiente para que você esteja recebendo muitos  feedbacks e solicitações de novos recursos. No entanto, ele também deve ser suportado por uma plataforma de portal que possa implementar essas novas funcionalidades sem um enorme comprometimento de recursos. O equilíbrio certo depende dos objetivos da sua organização, mas, na maioria dos casos, os benefícios de um portal para funcionários robusto mais do que se pagam.

  Isabella Rocha 2018-06-15T22:29:50Z
Categories: CMS, ECM

Euromoney’s integration journey presented at Gartner AADI London

SnapLogic - Fri, 06/15/2018 - 15:46

This year’s Gartner Application Architecture, Development & Integration Summit did not disappoint. The Gartner AADI Summit drew hundreds of senior business and IT leaders for two solid days, focusing on application architecture, agile development, and integration strategies. Not only did I get the opportunity to catch up with Gartner analysts and give a presentation, but[...] Read the full article here.

The post Euromoney’s integration journey presented at Gartner AADI London appeared first on SnapLogic.

Categories: ETL

It’s no myth: A guide to taming the hydra of enterprise integration

SnapLogic - Thu, 06/14/2018 - 15:20

As an experienced enterprise IT professional, you have undoubtedly faced a few Herculean challenges. Probably more than a dozen, even. But application integration is truly hydra-like: each enterprise integration project creates two interfaces that must be maintained to keep data flowing smoothly. Right now, your IT organization is fighting a losing battle, trying to maintain[...] Read the full article here.

The post It’s no myth: A guide to taming the hydra of enterprise integration appeared first on SnapLogic.

Categories: ETL

CiviRules release 2.0 is available

CiviCRM - Wed, 06/13/2018 - 07:50

CiviRules 2.0 is now available for you all!

We (Jaap Jansma, Klaas Eikelboom and me) are about to complete a little CiviRules sprint of 2 days (funded by CiviCooP). We have fixed quite a few issues, closed some that we thought were not applicable any more, introduced a few new conditions and actions and updated the documentation. And we are about to round it off with the new release CiviRules 2.0 and moving our repository to the CiviCRM GitLab server. We expect to do this tonight or tomorrow morning.

Categories: CRM

How to Open a PrestaShop E-commerce Website in China with PrestaShop

PrestaShop - Wed, 06/13/2018 - 05:16
Opening a PrestaShop e-commerce website in China can be one of the smartest moves for online entrepreneurs.
Categories: E-commerce

On your marks, ready, set … Launch!

Joomla! - Wed, 06/13/2018 - 04:00

The Joomla Project and CloudAccess.net are equally excited to announce the launch of launch.joomla.org, the brand new platform to launch a free Joomla website and test upcoming releases to support the project by helping make our CMS the best it can be.

Categories: CMS

Liferay Portal 7.1 Beta 3 Release

Liferay - Tue, 06/12/2018 - 16:52
I'm pleased to announce the immediate availability of: Liferay Portal 7.1 Beta 3
 
  Download Now: Fixed in Beta 3

New Features Summary

Modern Site Building: Liferay 7.1 introduces a new way of adding content.  Fragments allows a content author to create content in small reusable pieces.  Fragments can be edited in real time or can be exported and managed with the tooling of your choice.  Use page templates from within a site and have complete control over the layout of your content pages.  Navigation menus now give you complete control over site navigation.  Create site navigation in new and interesting ways and have full control over the navigations visual presentation.        Forms Experience: Liferay 7.1 includes a completely revamped forms experience.  Forms can now have complex grid layouts, numeric fields and file uploads. They now include new personalization rules that let you customize the default behavior of the form.  Using the new Element Sets, form creators can now create groups of reusable components.  Forms fields can now be translated into any language using any Liferay locale and can also be easily duplicated. 
    Redesigned System Settings: System Settings has received a complete overhaul.  Configurations have been logically grouped together making it easier than every before to find what's configurable.  Several options that were located on Server Administration have also been moved to System Settings.     User Administration: User account from has been completely redesigned.  Each form section can now be saved independently of each other minimizing the chance of losing changes.  The new ScreensNavigationEntry let's developers add any form they want to user administration.     Improvements to Blogs and Forums:  Blog readers a can now un-subscribe to notifications via email. Friendly URLs used to be generated based on the entries title. Authors now have complete control over the friendly URL of the entry.   Estimated reading time can be enabled in System Settings and will be calculated based on time taken to write an entry.     Blogs also have a new cards ADT that can be selected from the application configuration.  Videos can now be added inline while writing a new entry from popular services such as: Youtube, Vimeo, Facebook Video, and Twitch.  Message boards users can now attach as many files as they want by dragging and dropping them in a post.  Message boards also has had many visual updates.     Workflow Improvements: Workflow has received a complete UI overhaul.  All workflow configuration is now consolidated under one area in the Control Panel.  Workflow definitions are now versioned and previous versions can now be restored.  Workflow definitions can now be saved in draft form and published live when they are ready.     Infrastructure: Many improvements have been incorporated at the core platform level, including ElasticSearch 6.0 and the inclusion of Tomcat 9.0.  At the time of this release  JDK 8 is still the only supported JDK.   Documentation Documentation for Liferay 7.1 is well underway.  Many sections have already been completed in the Deployment and Development Sections.  For information on upgrading to 7.1 see the Upgrade Guide. Jamie Sammons 2018-06-12T21:52:00Z
Categories: CMS, ECM

Considerations for a strategic digital transformation road map

SnapLogic - Tue, 06/12/2018 - 15:36

For those who have missed it, we recently hosted the webcast “Considerations for a Strategic Digital Transformation Road Map,” with special guest, Paul Miller, a senior analyst from Forrester. Joining Paul was Diletta D’Onofrio, who heads digital transformation at SnapLogic. Some of the topics this webcast covers include where you may stand in your digital[...] Read the full article here.

The post Considerations for a strategic digital transformation road map appeared first on SnapLogic.

Categories: ETL

Liferay Portal 7.1 Beta 3 Release

Liferay - Tue, 06/12/2018 - 11:52
I'm pleased to announce the immediate availability of: Liferay Portal 7.1 Beta 3
 
  Download Now: Fixed in Beta 3

New Features Summary

Modern Site Building: Liferay 7.1 introduces a new way of adding content.  Fragments allows a content author to create content in small reusable pieces.  Fragments can be edited in real time or can be exported and managed with the tooling of your choice.  Use page templates from within a site and have complete control over the layout of your content pages.  Navigation menus now give you complete control over site navigation.  Create site navigation in new and interesting ways and have full control over the navigations visual presentation.        Forms Experience: Liferay 7.1 includes a completely revamped forms experience.  Forms can now have complex grid layouts, numeric fields and file uploads. They now include new personalization rules that let you customize the default behavior of the form.  Using the new Element Sets, form creators can now create groups of reusable components.  Forms fields can now be translated into any language using any Liferay locale and can also be easily duplicated. 
    Redesigned System Settings: System Settings has received a complete overhaul.  Configurations have been logically grouped together making it easier than every before to find what's configurable.  Several options that were located on Server Administration have also been moved to System Settings.     User Administration: User account from has been completely redesigned.  Each form section can now be saved independently of each other minimizing the chance of losing changes.  The new ScreensNavigationEntry let's developers add any form they want to user administration.     Improvements to Blogs and Forums:  Blog readers a can now un-subscribe to notifications via email. Friendly URLs used to be generated based on the entries title. Authors now have complete control over the friendly URL of the entry.   Estimated reading time can be enabled in System Settings and will be calculated based on time taken to write an entry.     Blogs also have a new cards ADT that can be selected from the application configuration.  Videos can now be added inline while writing a new entry from popular services such as: Youtube, Vimeo, Facebook Video, and Twitch.  Message boards users can now attach as many files as they want by dragging and dropping them in a post.  Message boards also has had many visual updates.     Workflow Improvements: Workflow has received a complete UI overhaul.  All workflow configuration is now consolidated under one area in the Control Panel.  Workflow definitions are now versioned and previous versions can now be restored.  Workflow definitions can now be saved in draft form and published live when they are ready.     Infrastructure: Many improvements have been incorporated at the core platform level, including ElasticSearch 6.0 and the inclusion of Tomcat 9.0.  At the time of this release  JDK 8 is still the only supported JDK.  
Documentation Documentation for Liferay 7.1 is well underway.  Many sections have already been completed in the Deployment and Development Sections.  For information on upgrading to 7.1 see the Upgrade Guide. Jamie Sammons 2018-06-12T16:52:54Z
Categories: CMS, ECM

Liferay Portal 7.1 Beta 3 Release

Liferay - Tue, 06/12/2018 - 11:52
I'm pleased to announce the immediate availability of: Liferay Portal 7.1 Beta 3
 
  Download Now: Fixed in Beta 3

New Features Summary

Modern Site Building: Liferay 7.1 introduces a new way of adding content.  Fragments allows a content author to create content in small reusable pieces.  Fragments can be edited in real time or can be exported and managed with the tooling of your choice.  Use page templates from within a site and have complete control over the layout of your content pages.  Navigation menus now give you complete control over site navigation.  Create site navigation in new and interesting ways and have full control over the navigations visual presentation.        Forms Experience: Liferay 7.1 includes a completely revamped forms experience.  Forms can now have complex grid layouts, numeric fields and file uploads. They now include new personalization rules that let you customize the default behavior of the form.  Using the new Element Sets, form creators can now create groups of reusable components.  Forms fields can now be translated into any language using any Liferay locale and can also be easily duplicated. 
    Redesigned System Settings: System Settings has received a complete overhaul.  Configurations have been logically grouped together making it easier than every before to find what's configurable.  Several options that were located on Server Administration have also been moved to System Settings.     User Administration: User account from has been completely redesigned.  Each form section can now be saved independently of each other minimizing the chance of losing changes.  The new ScreensNavigationEntry let's developers add any form they want to user administration.     Improvements to Blogs and Forums:  Blog readers a can now un-subscribe to notifications via email. Friendly URLs used to be generated based on the entries title. Authors now have complete control over the friendly URL of the entry.   Estimated reading time can be enabled in System Settings and will be calculated based on time taken to write an entry.     Blogs also have a new cards ADT that can be selected from the application configuration.  Videos can now be added inline while writing a new entry from popular services such as: Youtube, Vimeo, Facebook Video, and Twitch.  Message boards users can now attach as many files as they want by dragging and dropping them in a post.  Message boards also has had many visual updates.     Workflow Improvements: Workflow has received a complete UI overhaul.  All workflow configuration is now consolidated under one area in the Control Panel.  Workflow definitions are now versioned and previous versions can now be restored.  Workflow definitions can now be saved in draft form and published live when they are ready.     Infrastructure: Many improvements have been incorporated at the core platform level, including ElasticSearch 6.0 and the inclusion of Tomcat 9.0.  At the time of this release  JDK 8 is still the only supported JDK.   Documentation Documentation for Liferay 7.1 is well underway.  Many sections have already been completed in the Deployment and Development Sections.  For information on upgrading to 7.1 see the Upgrade Guide. Jamie Sammons 2018-06-12T16:52:00Z
Categories: CMS, ECM

CiviCRM 5.2.1 and 4.6.37 release

CiviCRM - Mon, 06/11/2018 - 12:20
This first CiviCRM release this summer is now ready to download.   RELEASE NOTES: Big thanks to Andrew Hunt from AGH Strategies for putting up together release notes for this version.  The release notes for 5.2.1 can be accessed here.   SPECIAL THANKS:
Categories: CRM

CiviCRM-OSDI Project Updates

CiviCRM - Mon, 06/11/2018 - 01:03

Hi everyone! I'm Andy, and I'm working on the OSDI integration project for GSoC 2018. If you want more information about what this project is all about, check out the GitLab here. The source code is here. The OSDI standard is a set of standards for interoperability between products in progressive movements. For more information about OSDI, click here.

Categories: CRM

Just added a small extension to allow many invoice addresses for a contact based on relationships

CiviCRM - Fri, 06/08/2018 - 12:04

I have just published the Invoice Address API (https://civicrm.org/extensions/invoice-address-api).

The development of the extension was funded by Domus Medica vzw. They are a membership organization specifically for GP's (general practitioner) in Flanders and Brussels.

Categories: CRM

Welcoming Software Heritage

Open Source Initiative - Fri, 06/08/2018 - 07:41

Coade Stone, like that used in the Nelson Pediment at the Old Royal Naval College, is a fantastic artificial rock whose creation process was lost for more than a century because it was kept secret. Software too is a precious part of our cultural heritage. OSI Board President Simon Phipps' spoke at the opening of Software Heritage at UNESCO, highlighting the importance of curating and sharing software, because sharing it guarantees its preservation in the very long term.

Distinguished guests, ladies and gentlemen, it is my pleasure to bring greetings from the Open Source Initiative, the global charity promoting open source and acting as steward of the open source definition and the list of approved licenses.

Open source is 20 years old. By popularising the pre-existing concepts of free software, it has been at the heart of the connected technology revolution. Open source gives developers permission in advance to collaborate and innovate regardless of affiliation. OSI-approved open source licenses are the hidden power behind Linux, Apache, Mozilla, Android and more.

But by granting all the rights necessary to us and our fellow community members to use, study, improve and share the software powering modern systems and networks, allowing us to collaborate with many “known others”, open source also unreservedly grants permission to “unknown others” to repurpose, rehost, reuse and revolutionise.

Availability to the outsider — to society in general — is crucial to our future. When software stays locked up inside the corporation or institution, when code created by the state with public funds remains secret, it does not add to our collective knowledge and is lost when its host moves on and the innovation it embodies is lost to society. This was the original motivation for previous generations to create temporary intellectual monopolies such as copyright, as an incentive to creators to make their creations public.

As time has passed, those intellectual monopolies have themselves been regarded as property and the knowledge and culture they embody is increasingly witheld from society. Open source allows that new-found wealth to be “spent” in a new way to stimulate collaboration. Collaboration in community has gone on to amplify innovation and accelerate adoption.

Software Heritage completes the new social contract enabled by open source. It provides the ultimate historical reference for the code behind our culture and comprehensive library of innovation to provide a “mounting block” to the shoulders of the giants before us. We should strive to get all the software that matters into this new digital Library of Alexandria.

It’s especially important that software funded with public money finds its way into Software Heritage. As Lessig observed, the practical experience of the law and of society is through code and all the software that governs our lives and liberty should be public code in this new library. More than just allowing us now to guard our freedoms, future historians will need source code to fully understand our digital present.

So as President of OSI, I warmly welcome the opening of Software Heritage. Open source delivers software freedom, and the Software Heritage archive takes the result and keeps it free for all time. That’s a great contribution to the modern world – congratulations!

Categories: Open Source

Why we need a new liferay-npm-bundler (3 of 3)

Liferay - Fri, 06/08/2018 - 03:15
A real life example of the use of bundler 2.x

This is the last of a three articles series motivating and explaining the enhancements we have done to Liferay's npm bundler. You can read previous article here.

To analyze how the bundler works we are going to examine a real life example comprising a portlet and an OSGi bundle providing Angular so that the portlet can import it. The project looks like this:

 npm-angular5-portlet-say-hello
     package.json
        {
            "name": "npm-angular5-portlet-say-hello",
            "version": "1.0.0",
            "main": "js/angular.pre.loader.js",
            "scripts": {
                "build": "tsc && liferay-npm-bundler"
            }
            …
        }
     tsconfig.json
        {
            "target": "es5",
            "moduleResolution": "node",
            …
        }
     .npmbundlerrc
        {
            …  
            "exclude": {
                "*": true
            },
            "config": {
                "imports": {
                    "npm-angular5-provider": {
                        "@angular/animations": "^5.0.0",
                        "@angular/cdk": "^5.0.0",
                        "@angular/common": "^5.0.0",
                        "@angular/compiler": "^5.0.0",
                        …
                    },
                    "": {
                        "npm-angular5-provider": "^1.0.0"
                    }
                }
            }
        }
     src/main/resources/META-INF/resources/css
         indigo-pink.css
            …  
     src/main/resources/META-INF/resources/js
         angular.pre.loader.ts
            // Bootstrap shims and providers
            import 'npm-angular5-provider';
            …  
        …
     npm-angular5-provider
         package.json
            {
                "name": "npm-angular5-provider",
                "version": "1.0.0",
                "main": "bootstrap.js",
                "scripts": {
                    "build": "liferay-npm-bundler"
                },
                "dependencies": {
                    "@angular/animations": "^5.0.0",
                    "@angular/cdk": "^5.0.0",
                    "@angular/common": "^5.0.0",
                    "@angular/compiler": "^5.0.0",
                    …
                }
                …
            }
     src/main/resources/META-INF/resources
         bootstrap.js
            /**
              * This file includes polyfills needed by Angular and must be loaded before the app.
            …  
            require('core-js/es6/reflect');
            require('core-js/es7/reflect');
            …
            require('zone.js/dist/zone');
            …
    …

You can find the whole project available for download here. Also, keep in mind that it is supposed to be run in Liferay 7.1.0 B2 at least (download it from here). It will not work in Liferay 7.0.0 unless you do some modifications!

As you can see, the portlet's build process includes calling the Typescript compiler (tsc) and then the bundler. We need to invoke tsc because Angular is based on the Typescript language and tsc is responsible for transpiling it to ES5. The Typescript compiler is configured in the tsconfig.json file and it is important that we set its output to es5 and its module resolution to node. That is because the bundler always expects that the input JS files are in those language and module formats.

Next, have a look at .npmbundlerrc where the imports for Angular are configured. Please note that we also import npm-angular5-provider with no namespace because we are going to invoke one of its modules to bootstrap Angular shims: see the angular.pre.loader.ts file, where npm-angular5-provider is imported. That import, in turn, loads npm-angular5-provider's main file (bootstrap.js).

Also, pay attention to the exclude section where every dependency of npm-angular5-portlet-say-hello is excluded to prevent Angular from appearing inside its JAR. This makes the build process faster and optimizes deployment but don't worry if you forget to exclude any unneeded dependency because nothing will fail: it just won't be used and will use a bit more space than needed.

The setup for npm-angular5-provider is very simple. It just declares Angular dependencies and invokes liferay-npm-bundler to bundle them. No need to do anything in this project. However, note how it also includes the bootstrap.js that is responsible for loading some shims needed by Angular. This file must always be invoked (by importing npm-angular5-provider from any portlet using it) before any portlet is run so that Angular doesn't fail because of missing APIs.

To finish with, check out the indigo-pink.css file of npm-angular5-portlet-say-hello. To keep this example simple, we have copied this file from the @angular/material npm package. It contains a prebuilt theme suitable for the Angular's Material Design widgets framework. In a real setup, that file's styles should be provided by a Liferay theme instead of being directly bundled inside each portlet needing it.

Now, suppose we run both builds. Let's see how the output would look like:

 npm-angular5-portlet-say-hello
     build/resources/main/META-INF/resources
         package.json
            {
                "dependencies": {
                    "@npm-angular5-provider$angular/animations": "^5.0.0",
                    "@npm-angular5-provider$angular/cdk": "^5.0.0",
                    "@npm-angular5-provider$angular/common": "^5.0.0",
                    "@npm-angular5-provider$angular/compiler": "^5.0.0",
            …
         js
             angular.loader.js
                "use strict";

                Liferay.Loader.define(
 ➥ "npm-angular5-portlet-say-hello@1.0.0/js/angular.loader",
 ➥ ['module', 'exports', 'require',
 ➥ '@npm-angular5-provider$angular/platform-browser-dynamic',
 ➥ './app.component',
 ➥ ...
 ➥ function (module, exports, require) {
                    var define = undefined;
                …
 npm-angular5-provider
     build/resources/main/META-INF/resources
         package.json
            {
                "name": "npm-angular5-provider",
                "version": "1.0.0",
                "main": "bootstrap.js",
                "dependencies": {
                    "@npm-angular5-provider$angular/animations": "^5.0.0",
                    "@npm-angular5-provider$angular/cdk": "^5.0.0",
                    "@npm-angular5-provider$angular/common": "^5.0.0",
                    "@npm-angular5-provider$angular/compiler": "^5.0.0",
                    …
                }
                …
            }
         bootstrap.js
            Liferay.Loader.define(
➥ 'npm-angular5-provider@1.0.0/bootstrap',
➥ ['module', 'exports', 'require',
➥ 'npm-angular5-provider$core-js/es6/reflect',
➥ ...
➥ function (module, exports, require) {
                var define = undefined;
                /**
                * This file includes polyfills needed by Angular and must be loaded before the app.
                …  
                require('npm-angular5-provider$core-js/es6/reflect');
                require('npm-angular5-provider$core-js/es7/reflect');
                …
                require('npm-angular5-provider$zone.js/dist/zone');
                …
            }
        …
         node_modules/npm-angular5-provider$core-js@2.5.7
             index.js
                Liferay.Loader.define(
➥ 'npm-angular5-provider$core-js@2.5.7/index',
➥ ['module', 'exports', 'require',
➥ './shim',
➥ ...
➥ function (module, exports, require) {
                    var define = undefined;
                    require('./shim');
            …
        …

Take a look at the output of npm-angular5-provider. As you can see, the bundler has copied the project and node_modules' JS files to the output and has wrapped them inside a Liferay.Loader.define() call so that the Liferay AMD Loader know how to handle them. Also, the module names in require() calls and inside the Liferay.Loader.define() dependencies array have been namespaced with the npm-angular5-provider$ prefix to achieve dependency isolation.

You may also have noted the var define = undefined; addition to the top of the file. This is introduced by liferay-npm-bundler to make the module think that it is inside a CommonJS environment (instead of an AMD one). This is because some npm packages are written in UMD format and, because we are wrapping it inside our AMD define() call, we don't want them to execute their own define() but prefer them to take the CommonJS path, where the exports are done through the module.exports global.

We have said that liferay-npm-bundler added these modifications but, to be fair, the real responsible is babel-plugin-wrap-modules-amd, a Babel plugin that is executed by Babel when it is invoked from the liferay-npm-bundler in one of its build phases.

If you are curious on how that plugin is configured, take a look at the default preset used by liferay-npm-bundler where the liferay-standard Babel preset is referenced which, in turn, configures the babel-plugin-wrap-modules-amd plugin.

Now, let's look at the package.json file and notice how the dependencies have been namespaced too. This is necessary to make the namespaced define() and require() calls work inside the JS modules, and it is done by the liferay-npm-bundler-plugin-namespace-packages plugin, configured here.

There are more plugins involved in the build that serve miscellaneous purposes. You can check their descriptions and use in the Liferay Docs.

Now let's see how the bundler has modified npm-angular5-portlet-say-hello. In this case we will only pay attention to the changes made in two files, because the rest is more or less the same as with the npm-angular5-provider.

First of all, the angular-loader.ts file has been converted to angular.loader.js. This has happened in two steps:

  1. The Typescript compiler transpiled angular-loader.ts to angular.loader.js generating a CommonJS module written in ECMAscript 5.
  2. The bundler then wrapped that code inside a Liferay.Loader.define() call to make it executable inside Liferay AMD Loader.

But more important: the module is importing Angular modules like @angular/platform-browser-dynamic which the bundler usually namespaces with the bundle's name (in this case npm-angular5-portlet-say-hello) but, because we are importing them from npm-angular5-provider, they have been namespaced with npm-angular5-provider$ instead so that they are loaded from that bundle at runtime (by the Liferay AMD Loader).

Finally, if you look at the dependencies inside the package.json file you will notice that the bundler has injected the ones pertaining to npm-angular5-provider to make them available at runtime.

And that's it. Shall this huge and boring series of articles help you understand how the new bundler works and how to leverage it to deploy your most exciting portlets.

Have fun coding!

Ivan Zaera 2018-06-08T08:15:24Z
Categories: CMS, ECM

Why we need a new liferay-npm-bundler (3 of 3)

Liferay - Fri, 06/08/2018 - 03:10
A real life example of the use of bundler 2.x

This is the last of a three articles series motivating and explaining the enhancements we have done to Liferay's npm bundler. You can read previous article here.

To analyze how the bundler works we are going to examine a real life example comprising a portlet and an OSGi bundle providing Angular so that the portlet can import it. The project looks like this:

 npm-angular5-portlet-say-hello
     package.json
        {
            "name": "npm-angular5-portlet-say-hello",
            "version": "1.0.0",
            "main": "js/angular.pre.loader.js",
            "scripts": {
                "build": "tsc && liferay-npm-bundler"
            }
            …
        }
     tsconfig.json
        {
            "target": "es5",
            "moduleResolution": "node",
            …
        }
     .npmbundlerrc
        {
            …  
            "exclude": {
                "*": true
            },
            "config": {
                "imports": {
                    "npm-angular5-provider": {
                        "@angular/animations": "^5.0.0",
                        "@angular/cdk": "^5.0.0",
                        "@angular/common": "^5.0.0",
                        "@angular/compiler": "^5.0.0",
                        …
                    },
                    "": {
                        "npm-angular5-provider": "^1.0.0"
                    }
                }
            }
        }
     src/main/resources/META-INF/resources/css
         indigo-pink.css
            …  
     src/main/resources/META-INF/resources/js
         angular.pre.loader.ts
            // Bootstrap shims and providers
            import 'npm-angular5-provider';
            …  
        …
     npm-angular5-provider
         package.json
            {
                "name": "npm-angular5-provider",
                "version": "1.0.0",
                "main": "bootstrap.js",
                "scripts": {
                    "build": "liferay-npm-bundler"
                },
                "dependencies": {
                    "@angular/animations": "^5.0.0",
                    "@angular/cdk": "^5.0.0",
                    "@angular/common": "^5.0.0",
                    "@angular/compiler": "^5.0.0",
                    …
                }
                …
            }
     src/main/resources/META-INF/resources
         bootstrap.js
            /**
              * This file includes polyfills needed by Angular and must be loaded before the app.
            …  
            require('core-js/es6/reflect');
            require('core-js/es7/reflect');
            …
            require('zone.js/dist/zone');
            …
    …

You can find the whole project available for download here. Also, keep in mind that it is supposed to be run in Liferay 7.1.0 B2 at least (download it from here). It will not work in Liferay 7.0.0 unless you do some modifications!

As you can see, the portlet's build process includes calling the Typescript compiler (tsc) and then the bundler. We need to invoke tsc because Angular is based on the Typescript language and tsc is responsible for transpiling it to ES5. The Typescript compiler is configured in the tsconfig.json file and it is important that we set its output to es5 and its module resolution to node. That is because the bundler always expects that the input JS files are in those language and module formats.

Next, have a look at .npmbundlerrc where the imports for Angular are configured. Please note that we also import npm-angular5-provider with no namespace because we are going to invoke one of its modules to bootstrap Angular shims: see the angular.pre.loader.ts file, where npm-angular5-provider is imported. That import, in turn, loads npm-angular5-provider's main file (bootstrap.js).

Also, pay attention to the exclude section where every dependency of npm-angular5-portlet-say-hello is excluded to prevent Angular from appearing inside its JAR. This makes the build process faster and optimizes deployment but don't worry if you forget to exclude any unneeded dependency because nothing will fail: it just won't be used and will use a bit more space than needed.

The setup for npm-angular5-provider is very simple. It just declares Angular dependencies and invokes liferay-npm-bundler to bundle them. No need to do anything in this project. However, note how it also includes the bootstrap.js that is responsible for loading some shims needed by Angular. This file must always be invoked (by importing npm-angular5-provider from any portlet using it) before any portlet is run so that Angular doesn't fail because of missing APIs.

To finish with, check out the indigo-pink.css file of npm-angular5-portlet-say-hello. To keep this example simple, we have copied this file from the @angular/material npm package. It contains a prebuilt theme suitable for the Angular's Material Design widgets framework. In a real setup, that file's styles should be provided by a Liferay theme instead of being directly bundled inside each portlet needing it.

Now, suppose we run both builds. Let's see how the output would look like:

 npm-angular5-portlet-say-hello
     build/resources/main/META-INF/resources
         package.json
            {
                "dependencies": {
                    "@npm-angular5-provider$angular/animations": "^5.0.0",
                    "@npm-angular5-provider$angular/cdk": "^5.0.0",
                    "@npm-angular5-provider$angular/common": "^5.0.0",
                    "@npm-angular5-provider$angular/compiler": "^5.0.0",
            …
         js
             angular.loader.js
                "use strict";

                Liferay.Loader.define(
 ➥ "npm-angular5-portlet-say-hello@1.0.0/js/angular.loader",
 ➥ ['module', 'exports', 'require',
 ➥ '@npm-angular5-provider$angular/platform-browser-dynamic',
 ➥ './app.component',
 ➥ ...
 ➥ function (module, exports, require) {
                    var define = undefined;
                …
 npm-angular5-provider
     build/resources/main/META-INF/resources
         package.json
            {
                "name": "npm-angular5-provider",
                "version": "1.0.0",
                "main": "bootstrap.js",
                "dependencies": {
                    "@npm-angular5-provider$angular/animations": "^5.0.0",
                    "@npm-angular5-provider$angular/cdk": "^5.0.0",
                    "@npm-angular5-provider$angular/common": "^5.0.0",
                    "@npm-angular5-provider$angular/compiler": "^5.0.0",
                    …
                }
                …
            }
         bootstrap.js
            Liferay.Loader.define(
➥ 'npm-angular5-provider@1.0.0/bootstrap',
➥ ['module', 'exports', 'require',
➥ 'npm-angular5-provider$core-js/es6/reflect',
➥ ...
➥ function (module, exports, require) {
                var define = undefined;
                /**
                * This file includes polyfills needed by Angular and must be loaded before the app.
                …  
                require('npm-angular5-provider$core-js/es6/reflect');
                require('npm-angular5-provider$core-js/es7/reflect');
                …
                require('npm-angular5-provider$zone.js/dist/zone');
                …
            }
        …
         node_modules/npm-angular5-provider$core-js@2.5.7
             index.js
                Liferay.Loader.define(
➥ 'npm-angular5-provider$core-js@2.5.7/index',
➥ ['module', 'exports', 'require',
➥ './shim',
➥ ...
➥ function (module, exports, require) {
                    var define = undefined;
                    require('./shim');
            …
        …

Take a look at the output of npm-angular5-provider. As you can see, the bundler has copied the project and node_modules' JS files to the output and has wrapped them inside a Liferay.Loader.define() call so that the Liferay AMD Loader know how to handle them. Also, the module names in require() calls and inside the Liferay.Loader.define() dependencies array have been namespaced with the npm-angular5-provider$ prefix to achieve dependency isolation.

You may also have noted the var define = undefined; addition to the top of the file. This is introduced by liferay-npm-bundler to make the module think that it is inside a CommonJS environment (instead of an AMD one). This is because some npm packages are written in UMD format and, because we are wrapping it inside our AMD define() call, we don't want them to execute their own define() but prefer them to take the CommonJS path, where the exports are done through the module.exports global.

We have said that liferay-npm-bundler added these modifications but, to be fair, the real responsible is babel-plugin-wrap-modules-amd, a Babel plugin that is executed by Babel when it is invoked from the liferay-npm-bundler in one of its build phases.

If you are curious on how that plugin is configured, take a look at the default preset used by liferay-npm-bundler where the liferay-standard Babel preset is referenced which, in turn, configures the babel-plugin-wrap-modules-amd plugin.

Now, let's look at the package.json file and notice how the dependencies have been namespaced too. This is necessary to make the namespaced define() and require() calls work inside the JS modules, and it is done by the liferay-npm-bundler-plugin-namespace-packages plugin, configured here.

There are more plugins involved in the build that serve miscellaneous purposes. You can check their descriptions and use in the Liferay Docs.

Now let's see how the bundler has modified npm-angular5-portlet-say-hello. In this case we will only pay attention to the changes made in two files, because the rest is more or less the same as with the npm-angular5-provider.

First of all, the angular-loader.ts file has been converted to angular.loader.js. This has happened in two steps:

  1. The Typescript compiler transpiled angular-loader.ts to angular.loader.js generating a CommonJS module written in ECMAscript 5.
  2. The bundler then wrapped that code inside a Liferay.Loader.define() call to make it executable inside Liferay AMD Loader.

But more important: the module is importing Angular modules like @angular/platform-browser-dynamic which the bundler usually namespaces with the bundle's name (in this case npm-angular5-portlet-say-hello) but, because we are importing them from npm-angular5-provider, they have been namespaced with npm-angular5-provider$ instead so that they are loaded from that bundle at runtime (by the Liferay AMD Loader).

Finally, if you look at the dependencies inside the package.json file you will notice that the bundler has injected the ones pertaining to npm-angular5-provider to make them available at runtime.

And that's it. Shall this huge and boring series of articles help you understand how the new bundler works and how to leverage it to deploy your most exciting portlets.

Have fun coding!

Ivan Zaera 2018-06-08T08:10:00Z
Categories: CMS, ECM

Customer Success Is the Key to Digital Transformation

Liferay - Thu, 06/07/2018 - 15:50

"Move fast and break things." Facebook's old motto has become an unintended slogan for the burgeoning tech culture and economy. One need look no further than the scandals swirling around so many truly innovative and disruptive companies to see how fast we've moved and how much we've broken. That's why at Gainsight our motto is "human-first." We make software for customer success.

Customer Success is a growing global movement in business—and not just software. It's not specifically an organization, department or job function, although many companies have "Customer Success Teams," "Customer Success Managers (CSMs)" and even "Chief Customer Officers (CCOs)." Many companies have sophisticated processes and tools for customer success. But all companies understand that the only way to keep your customers, expand their relationships with your company and attract new ones is to make sure they're getting what they pay for.

In other words, customer success is about ensuring your clients achieve their desired outcome with your product or service.

No digital transformation effort will be successful if it doesn't include that principle at its core.

Digital Transformation and the Subscription Economy

The global economy is in the middle stages of a fundamental shift away from one-time purchasing and perpetual licensing. Software is being infused into industries like manufacturing, agriculture, healthcare, services - everywhere. That's the basis for the digital transformation imperative causing upheaval in so many companies. And software is predominantly sold on a subscription basis.

But it's not just software. The subscription economy has come for our food, our clothes, even our shaving cream.

Why, though? It isn't as if there's a growing global demand for recurring payments. It's because, for the first time in the history of the modern economy, supply is greater than demand.

Supply > Demand

Throughout the 20th century, limitations in natural resources, means of production and other fundamental restrictions on how much stuff you can make and sell at maximum capacity created a natural balance between supply and demand. People want n widgets, Company A is capable of producing x widgets. The scarcest resource determines the cost and the supply.

But what happens when the fundamental unit of economic production is infinitely replicable? What happens when the widget is digital?

In the age of digital transformation, subscription economy and supply > demand, the scarcest resource isn't any raw material or finished goods. The scarcest resource is the customer.

And that means the key to success in the 21st century is protecting, nurturing, and growing your customer base. In other words, the key to your success is your customer's success.

Customer Success = Customer Outcomes + Customer Experience

Customers do not renew, expand or advocate if they are not achieving their desired outcome. But the quality of their interactions with your company will affect their level of success—and therefore their retention as well.

If customers are happy, it doesn't necessarily mean they'll renew. That's why it's called "customer success" and not "customer delight." But we do know that sentiment and satisfaction are correlated to the lifetime value of the customer. The economic imperative for maximizing that value is clear and was probably the impetus for your company's digital transformation.

But without a unified strategy for ensuring every customer a) achieves their desired outcome and b) has a great experience with your company, your customers' lifetime value will be limited.

Customer Success Is a Company-wide Imperative.

When you think about your customer's journey with your product or service, they interact constantly with different stakeholders throughout your organization. They'll have several "touchpoints" with marketers before they ever make a transaction. There will be interactions with salespeople, consultants, trainers, technical support, maybe even a CSM and a host of other people depending on the amount of touch you use with a typical client or segment of clients. And then there's the interaction with the product itself!

Your company has an opportunity to contribute to a customer's success at every touchpoint and every time they login to your product. But you can't leave the consistency or quality of those touchpoints up to chance. Read more in Gainsight's Guide to Company-Wide Customer Success.

The Periodic Table of Customer Success Elements

There's a science to customer success. Gainsight has implemented customer success people, process and technology at more than 500 companies, and we've learned a lot over the years about the best ways to make sure customers are successful.

There's too much to get into in this one blog, but we've identified 16 elements of a functional, company-wide customer success strategy. Now that you know the "why" of customer success, it's time to learn the "how."

Human-First Business

At the beginning of this blog, I said, "disruption needs disrupting." The word "disruption" has become synonymous with a software solution to a human problem. And humans do have weaknesses that technology can fix or make stronger. Most of us aren't great drivers. A lot of us could use some help with dating. The Hyperloop looks awesome.

But business? Business is about people. As you complete your digital transformation, your business will still be about people—only even more so. Your customers are your scarcest resource, and they are entirely comprised of humans!

If there's one mission-critical axiom for digital transformation, it's this: make customer success a cornerstone of your go-to-market plan.

But if there's two, then: never forget that underneath the data points and predictive algorithms are human beings—and that's what's at the heart of customer success.

Leverage Technology to Drive Customer Engagement

Existing customers are cheaper to market to than new ones and spend significantly more on products and services. Yet they often fall by the wayside as companies look to add new logos. Understand how to build deeper relationships with your existing customers and drive brand loyalty by leveraging new technologies in analytics and digital experience.

Read “Engage Existing Customers: Four Key Strategies”   Christine Reyes 2018-06-07T20:50:54Z
Categories: CMS, ECM

Helping brands increase awareness via integration and API creation

SnapLogic - Thu, 06/07/2018 - 13:18

What do brands like AirBnB, McDonald’s, Netflix, Apple, and PepsiCo have in common? These brands – and their visions and promises – are all instantly recognizable across today’s diverse mediums of print, digital, and media. What helps make that awareness happen? Their partnerships with TBWA Worldwide. As part of the Omnicom Group, TBWA is a[...] Read the full article here.

The post Helping brands increase awareness via integration and API creation appeared first on SnapLogic.

Categories: ETL

Why we need a new liferay-npm-bundler (2 of 3)

Liferay - Thu, 06/07/2018 - 02:12
How we are fixing the problems learned in bundler 2.x

This is the second of a three articles series motivating and explaining the enhancements we have done to Liferay's npm bundler. You can read the first article to find out the motivation for taking the actions explained in this one.

To solve the problems we saw in the previous article we followed this process:

  1. Go one step back in deduplication and behave like standard bundlers (webpack, Browserify, etc.).
  2. Fix peer dependencies support.
  3. Provide a way to deduplicate modules.

Step 1 gets us to a point where we have repeatable builds that mimick what the developer has in his node_modules folder when the projects are deployed to the server, and run in the browser. We implement it by isolating project dependencies (namespacing the packages) so that the dependencies of each project are isolated between them.

With that done, we get 2 nearly for free, because we just need to inject virtual dependencies in package.json files when peer dependencies are used. But this time we know which version constraints to use, because the project's whole dependency tree is isolated from other projects. That is, we have something similar to a node_modules folder available in the browser for each project.

Finally, because we have lost deduplication with steps 1 and 2 and that leads to a solution which is equivalent to standard bundlers, we define a way to deduplicate packages manually. This new way of deduplication is not automatic but leads to full control (during build time) of how each package is resolved.

Let's see the steps in more detail...

Isolation of dependencies

To achieve the isolation the new bundler just prefixes each package name with the name of the project and rewrites all the references in the JS files. For example, say you have our old beloved my-portlet project:

 package.json
    {
        "name": "my-portlet",
        "version": "1.0.0",
        "dependencies": {
            "isarray": "^1.0.0"
        }
    }
 node_modules/isarray
     package.json
        {
            "name": "isarray",
            "version": "1.0.1",
            "main": "index.js"
        }
 META-INF/resources
     view.jsp
        <aui:script require="my-portlet@1.0.0/js/main"/>
     js
         main.js
            require('isarray', function(isarray) {
                console.log(isarray([]));
            });

When we bundle it with bundler 1.x we get something like:

 META-INF/resources
     package.json
        {
            "name": "my-portlet",
            "version": "1.0.0",
            "dependencies": {
                "isarray": "^1.0.0"
            }
        }
     view.jsp
        <aui:script require="my-portlet@1.0.0/js/main"/>
     js
         main.js
            require('isarray', function(isarray) {
                console.log(isarray([]));
            });
     node_modules/isarray
         package.json
            {
                "name": "isarray",
                "version": "1.0.1",
                "main": "index.js"
            }

But if we use bundler 2.x it changes to:

 META-INF/resources
     package.json
        {
            "name": "my-portlet",
            "version": "1.0.0",
            "dependencies": {
                "my-portlet$isarray": "^1.0.0"
            }
        }
     view.jsp
        <aui:script require="my-portlet@1.0.0/js/main"/>
     js
         main.js
            require('my-portlet$isarray', function(isarray) {
                console.log(isarray([]));
            });
     node_modules/my-portlet$isarray
         package.json
            {
                "name": "my-portlet$isarray",
                "version": "1.0.1",
                "main": "index.js"
            }

As you see, we just needed to prepend my-portlet$ to each dependency package name and that way, each project will load its own dependencies and won't collide with any other project. Easy.

If we did now the same test of deploying my-portlet and his-portlet, each one would get its own versions simply because we have two different isarray packages: one called my-portlet$isarray and another called his-portlet$isarray.

Peer dependency support

Because we have isolated dependencies per portlet, we can now honor peer dependencies perfectly. For example, remember the Diverted peer dependencies section in the previous article: with bundler 1.x, there was only one a-library package available for everybody. But with the new namespacing technique, we have two a-librarys: my-portlet$a-library and his-portlet$a-library.

Thus, we can resolve peer dependencies exactly as stated in both projects because their names are prefixed with the project's name:

my-portlet@1.0.0 ➥ my-portlet$a-library 1.0.0 ➥ my-portlet$a-helper 1.0.0 his-portlet@1.0.0 ➥ his-portlet$a-library 1.0.0 ➥ his-portlet$a-helper 1.2.0

And in this case, my-portlet$a-library will depend on a-helper at version 1.0.0 (which is namespaced as my-portlet$a-helper) and his-portlet$a-library will depend on a-helper at version 1.2.0 (which is namespaced ashis-portlet$a-helper).

How does all this magic happen? Easy: we have just created a new bundler plugin named liferay-npm-bundler-plugin-inject-peer-dependencies that scans all JS modules for require calls and injects a virtual dependency in the package.json file when a module from an undeclared package is required.

So, for example, let's say you have the following project:

 META-INF/resources
     package.json
        {
            "name": "my-portlet",
            "version": "1.0.0",
            "dependencies": {
                "isarray": "^1.0.0"
            }
        }
     js
         main.js
            require(['isarray', 'isobject', function(isarray, isobject) {
                console.log(isarray([]));
                console.log(isobject([]));
            });
     node_modules
         isarray
             package.json
                {
                    "name": "isarray",
                    "version": "1.0.1",
                    "main": "index.js"
                }
         isobject
             package.json
                {
                    "name": "isobject",
                    "version": "1.1.0",
                    "main": "index.js"
                }

As you can see, there's no dependency to isobject in the package.json file.

However, if we run the project through the bundler configured with the inject-peer-dependencies plugin, it will find out that main.js is requiring isobject and will resolve it to version 1.1.0 which can be found in the node_modules folder.

After that, the plugin will inject a new dependency in the output package.json so that it looks like this:

{ "name": "my-portlet", "version": "1.0.0", "dependencies": { "isarray": "^1.0.0", "isobject": "1.1.0" } }

Note how, being an injected dependency, isobject's version constraints are its specific version number, without caret or any other semantic version operator. This makes sense, as we want to honor the exact peer dependency found in the project and thus we cannot inject a more relaxed semantic version expression because it could lead to unstable results.

Also keep in mind that these transformations are made in the output files (the ones in your build directory), not on your original source files.

Deduplication of packages (imports)

As we said before, the problem with namespacing is that each portlet is getting its own dependencies and we don't deduplicate any more. If we always used the bundler this way, it won't make too much sense, because we could obtain the same functionality with standard bundlers like webpack or Browserify and wouldn't need to rely on a specific tool like liferay-npm-bundler.

But, being Liferay a portlet based architecture, it would be quite useful if we could share dependencies among different portlets. That way, if a page is composed of five portlets using jQuery, only one copy of jQuery would be loaded by the JS interpreter to be used by the five different portlets.

With bundler 1.x that deduplication was made automagically, but we had no control over it. However, with version 2.x, we may now import packages from an external OSGi bundle, instead of using our own. That way, we can put shared dependencies in one project, and reference them from the rest.

Let's see an example: imagine that you have three portlets that use our favorite Non Existing Wonderful UI Components framework (WUI). Suppose this quite limited framework is composed of 3 packages:

  1. component-core
  2. button
  3. textfield

Now, say that we have these three portlets:

  1. my-toolbar
  2. my-menu
  3. my-content

Which we use to compose the home page of our site. And the three depend on the WUI framework.

If we just use the bundler to create three OSGi bundles, each one will package a copy of WUI inside it and use it when the page is rendered, thus leading to a page where your browser loads three different copies of WUI in the JS interpreter.

To avoid that, we will create a fourth bundle where WUI is packaged and import the WUI packages from the previous three bundles. This will lead to an structure like the following:

 my-toolbar
     .npmbundlerrc
        {
            "config": {
                "imports": {
                    "wui-provider": {
                        "component-core": "^1.0.0",
                        "button": "^1.0.0",
                        "textfield": "^1.0.0"
                    }
                }
            }
        }
 my-menu
     .npmbundlerrc
        {
            "config": {
                "imports": {
                    "wui-provider": {
                        "component-core": "^1.0.0",
                        "button": "^1.0.0",
                        "textfield": "^1.0.0"
                    }
                }
            }
        }
 my-content
     .npmbundlerrc
        {
            "config": {
                "imports": {
                    "wui-provider": {
                        "component-core": "^1.0.0",
                        "button": "^1.0.0",
                        "textfield": "^1.0.0"
                    }
                }
            }
        }
 wui-provider
     package.json
        {
            "name": "wui-provider",
            "dependencies": {
                "component-core": "^1.0.0",
                "button": "^1.0.0",
                "textfield": "^1.0.0"
            }
        }

As you can see, the three portlets declare the WUI imports in the .npmbundlerrc file. They would probably also be declared in the bundles' package.json files though they can be omitted too and it will still work in runtime because they are being imported.

So, how does this work? The key concept behind imports is that we switch the namespace of certain packages thus pointing them to an external bundle.

So, say that you have the following code in my-toolbar portlet:

var Button = require('button');

This would be transformed to the following when run through the bundler unconfigured:

var Button = require('my-toolbar$button');

But, because we are saying that button is imported from wui-provider, it will be changed to:

var Button = require('wui-provider$button');

And also, a dependency on wui-provider$button at version ^1.0.0 will be introduced in my-toolbar's package.json file so that the loader may look for the correct version.

And that's enough, because once we require wui-provider$button at runtime, we will jump to wui-provider's context and load the subdependencies from there on, even if we are executing code from my-toolbar.

If you give it a thought, that's logical because wui-provider's modules are namespaced too and once you load a module from it, it will keep requiring wui-provider$ prefixed modules all they way down.

So, that's pretty much the motivation and implementation of bundler 2.x. Hope it will shed some light on why we needed these changes and how we are now founding the npm SDK on much more stable roots.

You can now read the last article of the series analyzing a real life example of using bundler 2.0 within an Angular portlet.

Ivan Zaera 2018-06-07T07:12:39Z
Categories: CMS, ECM

Why we need a new liferay-npm-bundler (2 of 3)

Liferay - Thu, 06/07/2018 - 02:11
How we are fixing the problems learned in bundler 2.x

This is the second of a three articles series motivating and explaining the enhancements we have done to Liferay's npm bundler. You can read the first article to find out the motivation for taking the actions explained in this one.

To solve the problems we saw in the previous article we followed this process:

  1. Go one step back in deduplication and behave like standard bundlers (webpack, Browserify, etc.).
  2. Fix peer dependencies support.
  3. Provide a way to deduplicate modules.

Step 1 gets us to a point where we have repeatable builds that mimick what the developer has in his node_modules folder when the projects are deployed to the server, and run in the browser. We implement it by isolating project dependencies (namespacing the packages) so that the dependencies of each project are isolated between them.

With that done, we get 2 nearly for free, because we just need to inject virtual dependencies in package.json files when peer dependencies are used. But this time we know which version constraints to use, because the project's whole dependency tree is isolated from other projects. That is, we have something similar to a node_modules folder available in the browser for each project.

Finally, because we have lost deduplication with steps 1 and 2 and that leads to a solution which is equivalent to standard bundlers, we define a way to deduplicate packages manually. This new way of deduplication is not automatic but leads to full control (during build time) of how each package is resolved.

Let's see the steps in more detail...

Isolation of dependencies

To achieve the isolation the new bundler just prefixes each package name with the name of the project and rewrites all the references in the JS files. For example, say you have our old beloved my-portlet project:

 package.json
    {
        "name": "my-portlet",
        "version": "1.0.0",
        "dependencies": {
            "isarray": "^1.0.0"
        }
    }
 node_modules/isarray
     package.json
        {
            "name": "isarray",
            "version": "1.0.1",
            "main": "index.js"
        }
 META-INF/resources
     view.jsp
        <aui:script require="my-portlet@1.0.0/js/main"/>
     js
         main.js
            require('isarray', function(isarray) {
                console.log(isarray([]));
            });

When we bundle it with bundler 1.x we get something like:

 META-INF/resources
     package.json
        {
            "name": "my-portlet",
            "version": "1.0.0",
            "dependencies": {
                "isarray": "^1.0.0"
            }
        }
     view.jsp
        <aui:script require="my-portlet@1.0.0/js/main"/>
     js
         main.js
            require('isarray', function(isarray) {
                console.log(isarray([]));
            });
     node_modules/isarray
         package.json
            {
                "name": "isarray",
                "version": "1.0.1",
                "main": "index.js"
            }

But if we use bundler 2.x it changes to:

 META-INF/resources
     package.json
        {
            "name": "my-portlet",
            "version": "1.0.0",
            "dependencies": {
                "my-portlet$isarray": "^1.0.0"
            }
        }
     view.jsp
        <aui:script require="my-portlet@1.0.0/js/main"/>
     js
         main.js
            require('my-portlet$isarray', function(isarray) {
                console.log(isarray([]));
            });
     node_modules/my-portlet$isarray
         package.json
            {
                "name": "my-portlet$isarray",
                "version": "1.0.1",
                "main": "index.js"
            }

As you see, we just needed to prepend my-portlet$ to each dependency package name and that way, each project will load its own dependencies and won't collide with any other project. Easy.

If we did now the same test of deploying my-portlet and his-portlet, each one would get its own versions simply because we have two different isarray packages: one called my-portlet$isarray and another called his-portlet$isarray.

Peer dependency support

Because we have isolated dependencies per portlet, we can now honor peer dependencies perfectly. For example, remember the Diverted peer dependencies section in the previous article: with bundler 1.x, there was only one a-library package available for everybody. But with the new namespacing technique, we have two a-librarys: my-portlet$a-library and his-portlet$a-library.

Thus, we can resolve peer dependencies exactly as stated in both projects because their names are prefixed with the project's name:

my-portlet@1.0.0 ➥ my-portlet$a-library 1.0.0 ➥ my-portlet$a-helper 1.0.0 his-portlet@1.0.0 ➥ his-portlet$a-library 1.0.0 ➥ his-portlet$a-helper 1.2.0

And in this case, my-portlet$a-library will depend on a-helper at version 1.0.0 (which is namespaced as my-portlet$a-helper) and his-portlet$a-library will depend on a-helper at version 1.2.0 (which is namespaced ashis-portlet$a-helper).

How does all this magic happen? Easy: we have just created a new bundler plugin named liferay-npm-bundler-plugin-inject-peer-dependencies that scans all JS modules for require calls and injects a virtual dependency in the package.json file when a module from an undeclared package is required.

So, for example, let's say you have the following project:

 META-INF/resources
     package.json
        {
            "name": "my-portlet",
            "version": "1.0.0",
            "dependencies": {
                "isarray": "^1.0.0"
            }
        }
     js
         main.js
            require(['isarray', 'isobject', function(isarray, isobject) {
                console.log(isarray([]));
                console.log(isobject([]));
            });
     node_modules
         isarray
             package.json
                {
                    "name": "isarray",
                    "version": "1.0.1",
                    "main": "index.js"
                }
         isobject
             package.json
                {
                    "name": "isobject",
                    "version": "1.1.0",
                    "main": "index.js"
                }

As you can see, there's no dependency to isobject in the package.json file.

However, if we run the project through the bundler configured with the inject-peer-dependencies plugin, it will find out that main.js is requiring isobject and will resolve it to version 1.1.0 which can be found in the node_modules folder.

After that, the plugin will inject a new dependency in the output package.json so that it looks like this:

{ "name": "my-portlet", "version": "1.0.0", "dependencies": { "isarray": "^1.0.0", "isobject": "1.1.0" } }

Note how, being an injected dependency, isobject's version constraints are its specific version number, without caret or any other semantic version operator. This makes sense, as we want to honor the exact peer dependency found in the project and thus we cannot inject a more relaxed semantic version expression because it could lead to unstable results.

Also keep in mind that these transformations are made in the output files (the ones in your build directory), not on your original source files.

Deduplication of packages (imports)

As we said before, the problem with namespacing is that each portlet is getting its own dependencies and we don't deduplicate any more. If we always used the bundler this way, it won't make too much sense, because we could obtain the same functionality with standard bundlers like webpack or Browserify and wouldn't need to rely on a specific tool like liferay-npm-bundler.

But, being Liferay a portlet based architecture, it would be quite useful if we could share dependencies among different portlets. That way, if a page is composed of five portlets using jQuery, only one copy of jQuery would be loaded by the JS interpreter to be used by the five different portlets.

With bundler 1.x that deduplication was made automagically, but we had no control over it. However, with version 2.x, we may now import packages from an external OSGi bundle, instead of using our own. That way, we can put shared dependencies in one project, and reference them from the rest.

Let's see an example: imagine that you have three portlets that use our favorite Non Existing Wonderful UI Components framework (WUI). Suppose this quite limited framework is composed of 3 packages:

  1. component-core
  2. button
  3. textfield

Now, say that we have these three portlets:

  1. my-toolbar
  2. my-menu
  3. my-content

Which we use to compose the home page of our site. And the three depend on the WUI framework.

If we just use the bundler to create three OSGi bundles, each one will package a copy of WUI inside it and use it when the page is rendered, thus leading to a page where your browser loads three different copies of WUI in the JS interpreter.

To avoid that, we will create a fourth bundle where WUI is packaged and import the WUI packages from the previous three bundles. This will lead to an structure like the following:

 my-toolbar
     .npmbundlerrc
        {
            "config": {
                "imports": {
                    "wui-provider": {
                        "component-core": "^1.0.0",
                        "button": "^1.0.0",
                        "textfield": "^1.0.0"
                    }
                }
            }
        }
 my-menu
     .npmbundlerrc
        {
            "config": {
                "imports": {
                    "wui-provider": {
                        "component-core": "^1.0.0",
                        "button": "^1.0.0",
                        "textfield": "^1.0.0"
                    }
                }
            }
        }
 my-content
     .npmbundlerrc
        {
            "config": {
                "imports": {
                    "wui-provider": {
                        "component-core": "^1.0.0",
                        "button": "^1.0.0",
                        "textfield": "^1.0.0"
                    }
                }
            }
        }
 wui-provider
     package.json
        {
            "name": "wui-provider",
            "dependencies": {
                "component-core": "^1.0.0",
                "button": "^1.0.0",
                "textfield": "^1.0.0"
            }
        }

As you can see, the three portlets declare the WUI imports in the .npmbundlerrc file. They would probably also be declared in the bundles' package.json files though they can be omitted too and it will still work in runtime because they are being imported.

So, how does this work? The key concept behind imports is that we switch the namespace of certain packages thus pointing them to an external bundle.

So, say that you have the following code in my-toolbar portlet:

var Button = require('button');

This would be transformed to the following when run through the bundler unconfigured:

var Button = require('my-toolbar$button');

But, because we are saying that button is imported from wui-provider, it will be changed to:

var Button = require('wui-provider$button');

And also, a dependency on wui-provider$button at version ^1.0.0 will be introduced in my-toolbar's package.json file so that the loader may look for the correct version.

And that's enough, because once we require wui-provider$button at runtime, we will jump to wui-provider's context and load the subdependencies from there on, even if we are executing code from my-toolbar.

If you give it a thought, that's logical because wui-provider's modules are namespaced too and once you load a module from it, it will keep requiring wui-provider$ prefixed modules all they way down.

So, that's pretty much the motivation and implementation of bundler 2.x. Hope it will shed some light on why we needed these changes and how we are now founding the npm SDK on much more stable roots.

You can now read the last article of the series analyzing a real life example of using bundler 2.0 within an Angular portlet.

Ivan Zaera 2018-06-07T07:11:00Z
Categories: CMS, ECM
Syndicate content