Skip to content

kuno1/grab-deps

Repository files navigation

@kunoichi/grab-deps

A toolset to extract dependencies information from js/css files in WordPress Development.

日本語(Japanese) is also available.

TEST

This library dump wp-dependencies.json which includes dependencies and path information about js/css.

  • You don't have to specify dependencies from php files.
  • You can automate the registration & enqueue of assets.

Installation

npm install @kunoichi/grab-deps

Usage

Suppose that the directory structure of your theme/plugin is like below:

assets
- js
  - main.js
- css
  - style.css

Define tasks below in your npm scripts or gulpfile.js.

NPM Scripts

Since version 2.0.0, CLI inter face is available. If you need traspiling JavaScripts with @wordpress/scripts, add dependencies.

{
	"dependencies": {
		"@kunoichi/grab-deps": "^2.0.0",
		"@wordpress/sripts": "^27.0.0"
	},
	"scripts": {
		"dump": "grab-deps dump assets",
		"transpile": "grab-deps js src/js assets/js"
  }
}

grab-deps-image 'test/src/images/**/*.{jpg,png,gif,svg}' test/dist/images is also available. See peer dependencies for more information.

Gulp

// gulpfile.js
const gulp = require( 'gulp' );
const { dumpSetting } = require('@kunoichi/grab-deps');

// Dump task.
gulp.task( 'dump', function( done ) {
  dumpSetting( 'assets' );
  done();
} );

// Watch assets directory.
gulp.task( 'watch', function () {
  // Watch assets change and dump.
  gulp.watch( [ 'assets/**/*.css', 'assets/**/*.js' ], gulp.task( 'dump' ) );
} );

Now you can get updated dump information whatever changes you made for assets directory.

Register Assets in WordPress

Suppose that you have assets/js/app.js in your theme folder. Add @params in license comment.

/*!
 * My Plugin main JS
 * 
 * @handle my-plugin-app
 * @version 2.1.0
 * @footer false
 * @deps jquery, jquery-masonry, wp-i18n
 */
console.log( 'This script runs jQuery Masonry.' );

After adding @params to the license comment, run the script you set up earlier.

npm run dump

And you can get setting file wp-dependencies.json like this.

[
  {
    "handle": "my-plugin",
    "version": "2.1.0",
    "path": "assets/js/app.js",
    "hash": "5e84fd5b5817a6397aeef4240afeb97a",
    "deps": [ "jquery", "jquery-masonry", "wp-i18n" ],
    "ext": "js",
    "footer": true,
    "media": "all"
  }
]

Now you can bulk register assets through php.

// This code is in your theme's functions.php
add_action( 'init', function() {
    // Load setting as array.
    $settings = json_decode( file_get_contents( __DIR__ . '/wp-dependencies.json' ), true );
    // Register each setting.
    foreach ( $settings as $setting ) {
        $handle  = $setting['handle'];
        $version = $setting['hash']; // You can also specify @version
        $url     = get_template_directory_uri() . '/' . $setting['path'];
        if ( 'js' === $setting['ext'] ) {
            // Register JavaScript.
            $script_setting = [
            	'in_footer' => $setting['footer'],
            ];
            if ( in_array( $setting['strategy'], [ 'async', 'defer' ], true ) ) {
            	$script_setting['strategy'] = $setting['strategy'];
            }
            wp_register_script( $handle, $url, $setting['deps'], $version, $script_setting );
            // You can do extra settings here.
						// For example, set script translation.
						if ( in_array( 'wp-i18n', $setting['deps'], true ) ) {
							wp_set_script_translation();
						}
        } else {
            // This is CSS.
            wp_register_style( $handle, $url, $setting['deps'], $version, $setting['media'] ); 
        }
    }
} );

Now you can enqueue any of your scripts/styles with wp_enqueue_script( 'my-app-js' ) or wp_enqueue_style( 'my-blocks-alert-css' ).

ES Module Support (v3.0.0+)

Since version 3.0.0, grab-deps supports ES Module format with automatic global registration code generation. This feature enables you to write modern JavaScript modules while maintaining WordPress compatibility.

Benefits

  • Better Development Experience: Write standard ES Module syntax
  • Test-Friendly: Easy mocking and testing with Jest and other testing frameworks
  • IDE Support: Full autocomplete, type checking, and refactoring support
  • Single Codebase: Use the same code for development/testing (ES Modules) and production (global variables)

Configuration

Add configuration to your package.json with file-type specific settings:

{
  "grabDeps": {
    "namespace": "mylib",
    "js": {
      "srcDir": "src/js",
      "autoHandleGeneration": true,
      "globalExportGeneration": true
    },
    "css": {
      "srcDir": "src/css",
      "autoHandleGeneration": true
    }
  }
}

For legacy compatibility, you can still use the old format, but the new file-type specific format is recommended:

{
  "grabDeps": {
    "namespace": "mylib",
    "srcDir": "src",
    "autoHandleGeneration": true,
    "globalExportGeneration": true
  }
}

Configuration Options

Option Required Description
namespace Yes Namespace prefix for your library (e.g., "mylib" for @mylib/utils/date)
js.srcDir No JavaScript source directory (default: "src")
css.srcDir No CSS source directory (default: "src")
js.autoHandleGeneration No Auto-generate JS handle names based on folder structure
css.autoHandleGeneration No Auto-generate CSS handle names based on folder structure
js.globalExportGeneration No Generate global registration code for ES modules

Writing ES Modules

Write your JavaScript in standard ES Module format:

// src/utils/date.js
/*!
 * Date utility functions
 * @version 1.0.0
 */

export const formatDate = (date) => {
    return date.toISOString().split('T')[0];
};

export const parseDate = (dateString) => {
    return new Date(dateString);
};

export const addDays = (date, days) => {
    const result = new Date(date);
    result.setDate(result.getDate() + days);
    return result;
};

export default {
    formatDate,
    parseDate,
    addDays
};

Using Namespace Imports

Import modules using your configured namespace:

// src/app.js
/*!
 * Main application
 * @version 1.0.0
 */

import { formatDate, parseDate } from '@mylib/utils/date';
import { helper } from './helper.js';  // Relative imports work normally

export const initApp = () => {
    const today = new Date();
    console.log('Today:', formatDate(today));
    
    const parsed = parseDate('2023-01-15');
    console.log('Parsed:', parsed);
};

export const getAppVersion = () => {
    return '1.0.0';
};

Generated Output

When you run grab-deps dump, the tool will:

  1. Auto-generate handle names based on folder structure:

    • src/utils/date.jsmylib-utils-date
    • src/app.jsmylib-app
  2. Auto-detect dependencies from namespace imports:

    • @mylib/utils/datemylib-utils-date dependency
  3. Generate global registration code for browser compatibility:

// Auto-generated global registration code
window.mylib = window.mylib || {};
window.mylib.utils = window.mylib.utils || {};
window.mylib.utils.date = Object.assign(window.mylib.utils.date || {}, {
    formatDate: formatDate,
    parseDate: parseDate,
    addDays: addDays
});
window.mylib.utils.date.default = window.mylib.utils.date.default || {};

JSON Output Example

[
  {
    "handle": "mylib-utils-date",
    "path": "src/utils/date.js",
    "ext": "js",
    "version": "1.0.0",
    "deps": [],
    "footer": true,
    "media": "all",
    "strategy": "",
    "hash": "abc123...",
    "globalRegistration": "// Global registration code here..."
  },
  {
    "handle": "mylib-app",
    "path": "src/app.js",
    "ext": "js",
    "version": "1.0.0",
    "deps": ["mylib-utils-date"],
    "footer": true,
    "media": "all",
    "strategy": "",
    "hash": "def456...",
    "globalRegistration": "// Global registration code here..."
  }
]

WordPress Integration

The generated global registration code can be included in your WordPress theme/plugin to make ES modules available as global variables:

// In your theme/plugin
add_action('wp_enqueue_scripts', function() {
    $settings = json_decode(file_get_contents(__DIR__ . '/wp-dependencies.json'), true);
    
    foreach ($settings as $setting) {
        // Register the script normally
        wp_register_script(
            $setting['handle'],
            get_template_directory_uri() . '/' . $setting['path'],
            $setting['deps'],
            $setting['version'],
            ['in_footer' => $setting['footer']]
        );
        
        // Add global registration code if available
        if (!empty($setting['globalRegistration'])) {
            wp_add_inline_script($setting['handle'], $setting['globalRegistration'], 'after');
        }
    }
});

Development Workflow

  1. Write ES Modules: Use standard export/import syntax
  2. Run Tests: Use Jest or other testing frameworks directly on ES modules
  3. Build for Production: grab-deps generates WordPress-compatible global variables
  4. Deploy: WordPress loads scripts with global registration code

This approach gives you the best of both worlds: modern development experience with ES modules and WordPress compatibility through global variables.

Advanced Features

JavaScript Compilation with @wordpress/scripts Integration

The grab-deps js command provides advanced JavaScript compilation capabilities that override @wordpress/scripts functionality while leveraging webpack.config.js for enhanced processing.

Key Features:

  • ES6 Export Problem Resolution: Automatically handles empty compiled files from ES6-only exports (Issue #42 fix)
  • Global Registration Code Generation: Converts ES modules to globally accessible variables
  • Jest Environment Support: Uses globalThis for Node.js and browser compatibility
  • Dependency Auto-Detection: Extracts dependencies from asset.php files
  • Hierarchical Directory Processing: Maintains source directory structure
  • Improved Namespace Structure: Direct component access without filename clutter

Required webpack.config.js Setup

⚠️ Important: To enable ES module transformation and global registration, you must configure your project's webpack.config.js:

Option 1: Simple Setup (Recommended)

// webpack.config.js in your project root
const grabDepsConfig = require('@kunoichi/grab-deps/webpack.config.js');
module.exports = grabDepsConfig;

Option 2: Extending Existing Configuration

// webpack.config.js - if you have existing webpack configuration
const grabDepsConfig = require('@kunoichi/grab-deps/webpack.config.js');
const yourCustomConfig = {
  // Your existing webpack configuration...
};

module.exports = {
  ...grabDepsConfig,
  ...yourCustomConfig
};

Option 3: For Advanced Users

// webpack.config.js - if you need full control
const defaultConfig = require('@wordpress/scripts/config/webpack.config');
const path = require('path');

module.exports = {
  ...defaultConfig,
  module: {
    ...defaultConfig.module,
    rules: [
      // Add grab-deps loader before other loaders
      {
        test: /\.m?(j|t)sx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: path.resolve('./node_modules/@kunoichi/grab-deps/lib/webpack-loaders/namespace-transform-loader.js'),
          },
        ],
        enforce: 'pre',
      },
      // Include all existing rules
      ...defaultConfig.module.rules,
    ],
  },
};

Namespace Structure

The new namespace structure excludes filenames for cleaner component access:

// File: src/js/utils.js
export const formatDate = (date) => date.toISOString();
// Result: globalThis.mylib.formatDate

// File: src/js/components/modal.js  
export const Modal = () => { /* ... */ };
// Result: globalThis.mylib.components.Modal

// Usage in browser/WordPress:
mylib.formatDate(new Date());
mylib.components.Modal();

Jest Testing Support

The generated code uses globalThis instead of window, making it compatible with Jest and other Node.js testing environments:

// Works in both browser and Jest/Node.js environments
globalThis.mylib = globalThis.mylib || {};
globalThis.mylib.components = Object.assign(globalThis.mylib.components, {
    Modal: Modal,
    Button: Button
});

For detailed technical information about the compilation process, see: 📖 Architecture Documentation

Supported Header Info

Name Default type Target Possible Values
@version 0.0.0 String both 1.0.0
@handle Base file name without extension String both my-script
@deps Empty Array both [jquery, my-js]
@footer True Boolean js true or false
@strategy Empty String css defer,async
@media all String css screen, print
@cssmedia Same as @media. Avoid media query. String css screen, print

Tip

  1. All file will have hash property. This is md5 hash of file content and is useful and handy for version argument of wp_register_(script|style).
  2. If your CSS includes media query and grab-deps parsed it unintentionally, you can use @cssmedia to avoid it.

JSON Example

[
	{
		"path": "assets/js/app.js",
		"version": "0.0.0",
		"deps": [
			"jquery",
			"wp-api-fetch"
		],
		"hash": "900150983cd24fb0d6963f7d28e17f72",
		"strategy": "defer",
		"footer": true,
		"handle": "my-app"
	},
	{
		"path": "assets/css/style.css",
		"version": "0.0.0",
		"deps": [ "bootstrap" ],
		"hash": "900150983cd24fb0d6963f7d28e17f72",
		"media": "screen",
		"handle": "my-style"
	}
]

License text

Nowadays, some compilers/transpilers like webpack extract license comments. If original is like below:

/*!
 * Main app file.js
 *
 * @version 2.0.0
 */
console.log( 'Start rendering!' );

file.js will compiled like below:

console.log('Start rendering!');

And in same directory, file.js.LICENSE.txt will be exported.

/*!
 * Main app file.js
 *
 * @version 2.0.0
 */

In such case, @kunoichi/grab-deps will support .LISENCE.txt format by default. 3rd argument suffix of dumpSetting supports other format.

// If your JS license will be in `app.js.txt`,
// You can set suffix.
dumpSetting( 'assets', './wp-dependencies.json', '.txt' );
// If your licenses will be other format, specify function.
dumpSetting( 'assets', './wp-dependencies.json', function( path ) {
  // Convert path to your license.txt
  return licensePath;
} );

© 2019 TAROSKY

About

WordPress library to extract dependencies information from js/css files.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages