Use ESM with importmap to manage modern JavaScript in Laravel without transpiling or bundling

Overview

Logo Importmap Laravel

Total Downloads Latest Stable Version License

Introduction

Use ESM with importmap to manage modern JavaScript in Laravel without transpiling or bundling.

Inspiration

This package was inspired by the Importmap Rails gem. Some pieces of this README were copied straight from there and adapted to the Laravel version.

How does it work?

Import maps let you import JavaScript modules using logical names that map to versioned/digested files – directly from the browser. So you can build modern JavaScript applications using JavaScript libraries made for ES modules (ESM) without the need for transpiling or bundling. This frees you from needing Webpack, Yarn, npm, or any other part of the JavaScript toolchain.

With this approach you'll ship many small JavaScript files instead of one big JavaScript file. Thanks to HTTP/2 that no longer carries a material performance penalty during the initial transport, and in fact offers substantial benefits over the long run due to better caching dynamics. Whereas before any change to any JavaScript file included in your big bundle would invalidate the cache for the the whole bundle, now only the cache for that single file is invalidated.

There's native support for import maps in Chrome/Edge 89+, and a shim available for any browser with basic ESM support. So your app will be able to work with all the evergreen browsers.

Installation

You can install the package via composer:

composer require tonysm/importmap-laravel

The package has an install command that you may run to replace the default Laravel scaffold with one to use importmap:

php artisan importmap:install

Next, we need to add the following component to our view or layout file:

">
<x-importmap-tags entrypoint="app" />

Add that between your tags. The entrypoint should be the "main" file, commonly the resources/js/app.js file, which will be mapped to the app module (use the module name, not the file).

We also need to symlink the resources/js folder to public/js to make our JavaScript files publicly available. It's recommended to do that only for local development. This can be achieved by adding the link rule to your config/filesystems.php:



return [
    // ...
    'links' => array_filter([
        public_path('storage') => storage_path('app/public'),
        public_path('js') => env('APP_ENV') === 'local' ? resource_path('js') : null,
    ]),
];

Now, whenever you run php artisan storage:link in the local env, your resources/js folder will be linked to the public/js folder, which will make your imports work while you're developing your app.

For production, it's recommended to run the importmap:optimize command instead:

php artisan importmap:optimize

This should scan all your pinned files/folders (no URLs) and publish them to public/dist/js, adding a digest based on the file's content to the file name - so something like public/dist/js/app-123123.js, and then generate an importmap-manifest.json file in the public/ folder. This file will get precence over your pins. If you run that by accident in development, make sure you delete that file or simply run php artisan importmap:clear, which should get rid of it. You may also want to add /public/dist to your .gitignore file.

Usage

In a nutshell, importmaps works by giving the browser map of where to look for your JavaScript import statements. For instance, you could pin a dependency in the routes/importmap.php file for Alpinejs like so:



use Tonysm\ImportmapLaravel\Facades\Importmap;

// Other pins...
Importmap::pin("alpinejs", to: "https://ga.jspm.io/npm:[email protected]/dist/module.esm.js");

Then, in your JavaScript files you can safely do:

import Alpine from 'alpinejs';

Alpine.start();
window.Alpine = Alpine;

Pinning Local Files

Local pins should be added to the routes/importmap.php file manually, like so:

Importmap::pin("app", to: "/js/app.js");

This means that the app module will point to /js/app.js in the browser. This is a URL or a URI, not the path to file itself. Pins to local file assume a relative path of resources/js/ to find them.

Pinning Local Directories

Declaring all your local files can be tedious, so you may want to map an entire folder like so:

Importmap::pinAllFrom("resources/js/", to: "js/");

When we're generating the importmap JSON, we'll scan that directory looking for any .js or .jsm files inside of it and generating the correct importmap for them based on their relative location. There are a couple interesting rules, though, something like:

Path Module URI
resources/js/app.js app /js/app.js
resources/js/controllers/hello_controller.js controllers/hello_controller /js/controllers/hello_controller.js
resources/js/libs/index.js libs /js/libs/index.js

If there's an index.js file in a folder, we won't get index in the module name, so we can import it like

import libs from 'libs';

Instead of

import libs from 'libs/index';

Pinning External Dependencies

If you depend on any external library you can use the importmap:pin command to pin it, like so:

php artisan importmap:pin alpinejs

That will add the following line to your routes/importmap.php file:

Importmap::pin("alpinejs", to: "https://ga.jspm.io/npm:[email protected]/dist/module.esm.js");

The pin command makes use of the jspm.io API to resolve the dependencies (and the dependencies of our dependencies), looking for ESM modules that we can pin, and resolving it to a CDN URL. We can control the CDN we want to use by specifying the --from flag like so:

php artisan importmap:pin alpinejs --from=unpkg

Which should generate a pin like so:

Importmap::pin("alpinejs", to: "https://unpkg.com/[email protected]/dist/module.esm.js");

It's preferred that you always pin from the same CDN, because then your browser will reuse the same SSL handshake when downloading the files (which means they will be downloaded faster).

Alternatively to using CDNs, you may prefer to vendor the libraries yourself, which you can do by using the --download flag, like so:

php artisan importmap:pin alpinejs --download

This will resolve the dependencies (and the dependencies of our dependencies) and download all the files to your resources/js/vendor folder, which you should add to your version control and maintain yourself. The pin will look like this:

Importmap::pin("alpinejs", to: "/js/vendor/alpinejs.js"); // @3.8.1

The version is added as a comment to your pin so you know which version was imported. Don't remove that as it's gonna be useful later on when you need to upgrade your dependencies.

Preloading Modules

To avoid the waterfall effect where the browser has to load one file after another before it can get to the deepest nested import, we support modulepreload links. Pinned modules can be preloaded by appending preload: true to the pin, like so:

Importmap::pinAllFrom("resources/js/", to: "js/", preload: true);
Importmap::pin("alpinejs", to: "https://unpkg.com/[email protected]/dist/module.esm.js", preload: true); // @3.8.1

Which will add the correct links tags to your head tag in the HTML document, like so:

">
<link rel="modulepreload" href="https://unpkg.com/[email protected]/dist/module.esm.js">

Known Problems

Browser Console Errors

While import maps are native in Chrome and Edge, they need a shim in other browsers that'll produce a JavaScript console error like TypeError: Module specifier, 'app' does not start with "/", "./", or "../".. This error is normal and does not have any user-facing consequences.

In Firefox, when opening the browser console, the asm.js module lexer build will run in unoptimized mode due to the debugger attaching. This gives a warning message "asm.js type error: Disabled because no suitable wasm compiler is available" which is as expected. When the console is closed again, the asm.js optimizations are fully applied, and this can even be verified with the console open by disabling the debugger in about:config and reloading the page.

On React's JSX and Vue's SFC

It's possible to use both React and Vue with importmaps, but unfortunatelly you would have to use those without the power of JSX or SFC. That's because those file types need a compilation/transpilation step where they are converted to something the browser can understand. There are alternative ways to use both these libraries, but I should say that these are not "common" ways on their communities. You may use React with HTM. And you can use Vue just fine without SFC, the only difference is that your templates would be in Blade files, not a SFC file.

Process ENV Configs

You may be used to having a couple process.env.MIX_* lines in your JS files here and there. The way this works is Webpack would replace at build time your calls to process.env with the values it had during the build. Since we don't have a "build time" anymore, this won't work. Instead, you should add tags to your layout file with anything that you want to make available to your JavaScript files and use document.head.querySelector('meta[name=my-config]').content instead of relying in the process.env.

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

Comments
Releases(1.3.0)
  • 1.3.0(Dec 28, 2022)

    Changelog

    • CHANGED: Bumped es-module-shims version to 1.6.2 (latest) and make it configurable so applications may bump it without having to upgrade the package
    Source code(tar.gz)
    Source code(zip)
  • 1.2.3(Aug 4, 2022)

    Changelog

    • FIXED: Fixes the optimize command when pinning dependencies from public/vendor (https://github.com/tonysm/importmap-laravel/commit/a3a685583bfaaf82e737f0ec2fb368f63f3d3c1f)
    Source code(tar.gz)
    Source code(zip)
  • 1.2.2(Aug 4, 2022)

    Changelog

    • CHANGED: stop escaping the slashes in the importmap:json output (https://github.com/tonysm/importmap-laravel/commit/496cb8bc77c51fd1dae28f12e37a881b4cc41997)
    • FIXED: handle imported files from public/vendor folder (https://github.com/tonysm/importmap-laravel/commit/b6c22d1f047715b1f47393dc55a59730397aa55a)
    Source code(tar.gz)
    Source code(zip)
  • 1.2.1(Jul 29, 2022)

    Changelog

    • CHANGED: we don't delete the public/js folder anymore, but instead ask the developer to do so (https://github.com/tonysm/importmap-laravel/commit/f0b3ad562bb748fe20f34768d8b9fb49936099c7)
    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Jul 3, 2022)

    Changelog

    • CHANGED: The importmap:install command was changed to work with the new Vite setup in Laravel. It should also still work on installs in the Laravel 8 frontends setups using Mix.
    Source code(tar.gz)
    Source code(zip)
  • 1.1.1(Jun 30, 2022)

    Changelog

    • FIXED: The importmap:pin command was breaking depending on the package name because we needed to wrap the package name using the preg_quote to escape it. Otherwise, some characters might become part of the regex itself. https://github.com/tonysm/importmap-laravel/pull/16
    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(Jun 27, 2022)

    Changelog

    • Bumps es-module-shims to version 1.5.8 (https://github.com/tonysm/importmap-laravel/commit/21db811ff837bf384f3331d7d47aa692839ad230)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(May 30, 2022)

    Changelog

    • NEW: Adds importmap:audit and importmap:outdated commands (https://github.com/tonysm/importmap-laravel/pull/14)
    • CHANGED: Bumps the es-module-shims dependency (https://github.com/tonysm/importmap-laravel/pull/14)
    Source code(tar.gz)
    Source code(zip)
  • 0.4.1(Feb 13, 2022)

    Changelog

    • FIXED: Pinned directories were not working on Windows because we're using / instead of \. Anyways, that should be fixed now. Define the directories with / as you would on any Unix/Linux OS and the package will make sure that gets converted to the correct directory separator when dealing with file paths and to the / separator when dealing with URIs https://github.com/tonysm/importmap-laravel/pull/5
    Source code(tar.gz)
    Source code(zip)
  • 0.4.0(Feb 13, 2022)

    Changelog

    • CHANGED: Changes the manifest filename to be .importmap-manifest.json (with a dot prefix) so it can be included in the Vapor artifact (which doesn't remove dotfiles by default).
    • ADDED: Adds a new config entry for the manifest location. If you have published your importmap.php config file, make sure you re-publish that (no need to publish it if you haven't done that)
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Feb 9, 2022)

  • 0.2.0(Jan 27, 2022)

    Changelog

    • FIXED: The manifest already had the final asset URL on it, which is handled by the optimize command, so we don't need to call the asset resolver when the manifest exists
    • NEW: Added an AssetResolver invokable class which should add a ?digest=$HASH to the asset URL, which is useful for cache busting while in local development. This won't be used in production as the optimize command already generates the full URLs there, which means the AssetResolver won't be called
    • CHANGED: The entrypoint was made optional and it defaults to the app module, which matches the "entrypoint" file in the default Laravel install (resources/js/app.js)
    Source code(tar.gz)
    Source code(zip)
Owner
Tony Messias
Tony Messias
Use Blade templates without the full Laravel framework

blade Use Laravel Blade templates as a standalone component without the full Laravel framework Full documentation is available at http://duncan3dc.git

Craig Duncan 138 Dec 7, 2022
Register for multiple Livestorm sessions from an external form. Practical use of Livestorm API with PHP/Javascript.

Livestorm Multi Session Registration Register for multiple Livestorm sessions from an external form. Practical use of Livestorm API with PHP/Javascrip

Nathan CHEVALIER 0 Dec 24, 2021
A Laravel Wrapper for the CoinDCX API. Now easily connect and consume the CoinDCX Public API in your Laravel apps without any hassle.

This package provides a Laravel Wrapper for the CoinDCX API and allows you to easily communicate with it. Important Note This package is in early deve

Moinuddin S. Khaja 2 Feb 16, 2022
Laravel blade directives and php helpers for serverside rendered content, based on browser window size WITHOUT css

Laravel Window Size and Breakpoints Laravel blade directives and php helpers for server side rendered content, based on browser window size WITHOUT cs

Tina Hammar 7 Nov 23, 2022
Chain Laravel jobs without having to glue it to a starting job

Laravel Job Chainer JobChainer does chain a variable amount of jobs by adding them with the add() method. This makes it possible to chain jobs without

Just Iversen 69 Nov 18, 2022
A package for Laravel One Time Password (OTP) generator and validation without Eloquent Model, since it done by Cache.

Laravel OTP Introduction A package for Laravel One Time Password (OTP) generator and validation without Eloquent Model, since it done by Cache. The ca

Lim Teck Wei 52 Sep 6, 2022
Laravel blade directives and php helpers for serverside rendered content, based on browser window size WITHOUT css. Requires Livewire and AlpineJS.

Laravel Livewire Window Size and Breakpoints Laravel blade directives and php helpers for server side rendered content, based on browser window size W

Tina Hammar 15 Oct 6, 2022
In-place pagination without page refresh by using Backbone.js with Laravel PHP framework

Laravel Backbone - based in-place pagination demo Store application See demo at: http://demos.maxoffsky.com/ajax-pagination/ Tutorial at: http://maxof

Maksim Surguy 41 Oct 11, 2022
LaravelFly is a safe solution to speeds up new or old Laravel 5.5+ projects, with preloading and coroutine, while without data pollution or memory leak

Would you like php 7.4 Preloading? Would you like php coroutine? Today you can use them with Laravel because of Swoole. With LaravalFly, Laravel will

null 456 Dec 21, 2022
Generate robust laravel athorization without writing a single line of code.

Implement robust laravel authorization logic without writing a single line of code This package helps you to quickly create strong policy authorizatio

Flixtechs 29 Oct 15, 2022
A modern solution for running Laravel Horizon with a CRON-based supervisor.

A modern solution for running Laravel Horizon with a cron-based supervisor This Laravel package automatically checks every three minutes if your Larav

Ralph J. Smit 31 Dec 9, 2022
Laravel routes from Javascript

Laravel Javascript Routes Why? I love the Laravel 4 routing system and I often use named routes like route('users.show', array('id' => 1)) to generate

Fede Isas 63 Oct 10, 2022
A BEAUTIFUL, RESPONSIVE, CUSTOMIZABLE, ACCESSIBLE (WAI-ARIA) REPLACEMENT FOR JAVASCRIPT'S POPUP BOXES FOR LARAVEL

A BEAUTIFUL, RESPONSIVE, CUSTOMIZABLE, ACCESSIBLE (WAI-ARIA) REPLACEMENT FOR JAVASCRIPT'S POPUP BOXES FOR LARAVEL Install To get started with SweetAle

Rashid Ali 939 Jan 8, 2023
A Laravel clone of the Javascript Flatpickr (Date picker) library

A Laravel clone of the Javascript Flatpickr (Date picker) library Using this package you can add a beautiful date or datetime picker into your project

Laratips 49 Dec 28, 2022
Video Chat application built using Metered Video SDK, with PHP Laravel Backend and JavaScript Front-End

Group Video Chat App with PHP Laravel and JavaScript Powered by Metered Video SDK Overview This application is a highly scalable group video calling a

null 2 Aug 18, 2022
A simple to use query builder for the jQuery QueryBuilder plugin for use with Laravel.

QueryBuilderParser Status Label Status Value Build Insights Code Climate Test Coverage QueryBuilderParser is designed mainly to be used inside Laravel

Tim Groeneveld 149 Nov 11, 2022
A Laravel package that allows you to use multiple ".env" files in a precedent manner. Use ".env" files per domain (multi-tentant)!

Laravel Multi ENVs Use multiple .envs files and have a chain of precedence for the environment variables in these different .envs files. Use the .env

Allyson Silva 48 Dec 29, 2022
A simple and modern approach to stream filtering in PHP

clue/stream-filter A simple and modern approach to stream filtering in PHP Table of contents Why? Support us Usage append() prepend() fun() remove() I

Christian Lück 1.5k Dec 29, 2022
Lavacharts is a graphing / charting library for PHP 5.4+ that wraps Google's Javascript Chart API.

Lavacharts 3.1.12 Lavacharts is a graphing / chart library for PHP5.4+ that wraps the Google Chart API. Stable: Dev: Developer Note Please don't be di

Kevin Hill 616 Dec 17, 2022