• Stars
    star
    116
  • Rank 303,894 (Top 6 %)
  • Language
    PHP
  • License
    MIT License
  • Created almost 3 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

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 />

Add that between your <head> 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).

By default the x-importmap-tags component assumes your entrypoint module is app, which matches the existing resources/js/app.js file from Laravel's defaulf scaffolding. You may want to customize the entrypoint, which you can do with the entrypoint prop:

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

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:

<?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 a .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, as well as *importmap-manifest.json.

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:

<?php

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';

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">

Dependency Maintenance Commands

Maintaining a healthy dependency list can be tricky. Here's a couple of commands to help you with this task.

Outdated Dependencies

To keep your dependencies up-to-date, make sure you run the importmap:outdated command from time to time:

php artisan importmap:outdated

This command will scan your config/importmap.php file, find your current versions, then use the NPM registry API to look for the latest version of the packages you're using. It also handles locally served vendor libs that you added using the --download flag from the importmap:pin command.

Auditing Dependencies

If you want to a security audit on your dependecies to see if you're using a version that's been breached, run the importmap:audit command from time to time. Better yet, add that command to your CI build:

php artisan importmap:audit

This will also scan your config/importmap.php file, find your current versions, then use the NPM registry API to look for vulnerabilities on your packages. It also handles locally serverd vendor libs that you added using the --download flag from the importmap:pin command.

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 <meta> 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.

More Repositories

1

turbo-laravel

This package gives you a set of conventions to make the most out of Hotwire in Laravel.
PHP
673
star
2

rich-text-laravel

Integrates the Trix Editor with Laravel. Inspired by the Action Text gem from Rails.
PHP
309
star
3

tailwindcss-laravel

This package wraps up the standalone executable version of the Tailwind CSS framework for a Laravel application.
PHP
254
star
4

slackish-laravel

Implementing a chat app in Laravel
PHP
112
star
5

turbo-demo-app

This is a demo application that uses Turbo Laravel.
PHP
56
star
6

local-time-laravel

This is a Laravel port of the local_time gem from Basecamp.
PHP
56
star
7

globalid-laravel

Identify app models with a URI. Inspired by the globalid gem.
PHP
37
star
8

laravel-elasticsearch-2019

PHP
29
star
9

laravel-paratest

PHP
14
star
10

laradock-cli

This is an experimentation app.
PHP
13
star
11

mychat-php

A chat app using Websocket with Ratchet (not using NodeJS)
PHP
12
star
12

dockerforlaravel-k8s-files

Shell
7
star
13

phredis-server

A naive implementation of Redis protocol in PHP with Amphp (async)
PHP
7
star
14

ldap-server-laravel

PHP
5
star
15

turbo-ram

PHP
4
star
16

stimulus-laravel

Use Stimulus in your Laravel app
PHP
4
star
17

fiap-tech-challenge-1-pedidos

TypeScript
4
star
18

laravel-docker-preset

PHP
3
star
19

laravel-eventsourcing

PHP
3
star
20

dockerforlaravel-app

PHP
3
star
21

laravel-inertia-react

PHP
3
star
22

nodejs-multiuser-chat

An example of using NodeJS and SocketIO in a multi-user chat environment..
3
star
23

ibis-docker

Docker wrapper for the themsaid/ibis e-book generator.
Dockerfile
2
star
24

spa-oauth2-laravel

PHP
2
star
25

laravel-queue-async-apps-example

PHP
2
star
26

turbo-ram-android

Kotlin
2
star
27

cake-i18n-example

Exemplo de Internacionalização i18n
PHP
2
star
28

turbo-laravel-test-helpers

This package adds a couple macros and assertion helpers to your application using Turbo Laravel.
PHP
1
star
29

lara-journal-rich-text-demo

PHP
1
star
30

laravel-docker-demo

PHP
1
star
31

rich-text-demo-app

PHP
1
star
32

swagger-laravel-example

PHP
1
star
33

delegated-types-buckets-recordings

PHP
1
star
34

mytwitter-laravel4

MyTwitter, using Laravel, Travis-CI, Pagodabox (for stage) and Flat-UI
PHP
1
star
35

commands-and-domain-events-tickets

PHP
1
star
36

todo-app-angular-laravel4

Todo App using Angular and Laravel4 (as a RESTful API)
Ruby
1
star
37

laravel-bank-ar

An example application of reification
PHP
1
star
38

turbo-laravel-bootstrap-example

CSS
1
star
39

livewire-demos

PHP
1
star