Skip to content

Module System ​

Organize and structure your NullScript code with an intuitive module system

NullScript provides a clean and expressive module system using use, share, and from keywords. This guide covers everything from basic imports to advanced module patterns.

Core Module Keywords ​

NullScriptJavaScriptDescription
useimportImport modules and exports
shareexportExport functions, classes, and values
fromfromSpecify import source (unchanged)

Basic Module Operations ​

Exporting Values ​

javascript
// math.ns
share run add(a, b) {
    return a + b;
}

share run subtract(a, b) {
    return a - b;
}

share fixed PI = 3.14159;
share fixed E = 2.71828;

// Default export
share default run multiply(a, b) {
    return a * b;
}

Importing Values ​

javascript
// main.ns
use { add, subtract, PI } from './math.ns';
use multiply from './math.ns';  // Default import

run calculate() {
    let sum = add(5, 3);
    let difference = subtract(10, 4);
    let product = multiply(6, 7);

    speak.say(`Sum: ${sum}, Difference: ${difference}, Product: ${product}`);
    speak.say(`PI: ${PI}`);
}

Advanced Import Patterns ​

Named Imports ​

javascript
// utils.ns
share run formatDate(date) {
    return date.toLocaleDateString();
}

share run validateEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

share run capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
}

// main.ns
use { formatDate, validateEmail, capitalize } from './utils.ns';

run processUser(user) {
    let formattedDate = formatDate(user.createdAt);
    let isValidEmail = validateEmail(user.email);
    let displayName = capitalize(user.name);

    return { formattedDate, isValidEmail, displayName };
}

Default Imports ​

javascript
// User.ns
model User {
    __init__(name, email) {
        self.name = name;
        self.email = email;
        self.createdAt = new Date();
    }

    run getDisplayName() {
        return self.name;
    }
}

share default User;

// main.ns
use User from './User.ns';

run createUser(name, email) {
    return fresh User(name, email);
}

Mixed Imports ​

javascript
// react-components.ns
use React from 'react';
share { Button, Input, Modal } from './components.ns';
share default App from './App.ns';

// main.ns
use React, { Button, Input, Modal } from './react-components.ns';
use App from './react-components.ns';

Namespace Imports ​

javascript
// math-utils.ns
share run add(a, b) { return a + b; }
share run subtract(a, b) { return a - b; }
share run multiply(a, b) { return a * b; }
share run divide(a, b) { return a / b; }

// main.ns
use * as MathUtils from './math-utils.ns';

run calculate() {
    let sum = MathUtils.add(10, 5);
    let product = MathUtils.multiply(4, 6);

    return { sum, product };
}

Re-exporting ​

javascript
// index.ns - Barrel export
share { add, subtract } from './math.ns';
share { formatDate, validateEmail } from './utils.ns';
share { User, Admin } from './models.ns';
share default App from './App.ns';

// main.ns
use { add, formatDate, User } from './index.ns';

Module Organization Patterns ​

Feature-Based Organization ​

src/
├── features/
│   ├── auth/
│   │   ├── auth.ns          # Auth logic
│   │   ├── auth.types.ns    # Auth types
│   │   ├── auth.utils.ns    # Auth utilities
│   │   └── index.ns         # Barrel export
│   ├── users/
│   │   ├── user.ns          # User logic
│   │   ├── user.types.ns    # User types
│   │   └── index.ns         # Barrel export
│   └── posts/
│       ├── post.ns          # Post logic
│       └── index.ns         # Barrel export
├── shared/
│   ├── utils.ns             # Common utilities
│   ├── constants.ns         # Shared constants
│   └── types.ns             # Shared types
└── main.ns                  # Main application

Layer-Based Organization ​

src/
├── presentation/             # UI components
│   ├── components.ns
│   ├── pages.ns
│   └── layouts.ns
├── business/                 # Business logic
│   ├── services.ns
│   ├── validators.ns
│   └── transformers.ns
├── data/                     # Data access
│   ├── repositories.ns
│   ├── api.ns
│   └── storage.ns
└── domain/                   # Domain models
    ├── entities.ns
    ├── value-objects.ns
    └── enums.ns

Module Configuration ​

Package.json Configuration ​

json
{
  "name": "my-nullscript-app",
  "type": "module",
  "exports": {
    ".": "./src/index.ns",
    "./components": "./src/components/index.ns",
    "./utils": "./src/utils/index.ns"
  },
  "imports": {
    "#internal/*": "./src/internal/*.ns",
    "#shared/*": "./src/shared/*.ns"
  }
}

Import Maps (Browser) ​

html
<script type="importmap">
  {
    "imports": {
      "nullscript/": "/node_modules/nullscript/",
      "@app/": "/src/",
      "@shared/": "/src/shared/"
    }
  }
</script>

<script type="module">
  import { User } from "@app/models/User.ns";
  import { utils } from "@shared/utils.ns";
</script>

Advanced Module Patterns ​

Dynamic Imports ​

javascript
run later loadModule(moduleName) {
    test {
        let module = hold import(`./modules/${moduleName}.ns`);
        return module;
    } grab (error) {
        speak.scream(`Failed to load module ${moduleName}:`, error.message);
        return null;
    }
}

// Usage
run later example() {
    let userModule = hold loadModule('user');
    if (userModule) {
        let User = userModule.default;
        return fresh User("Alice", "alice@example.com");
    }
}

Conditional Imports ​

javascript
// feature-flags.ns
share fixed FEATURES = {
    advancedSearch: true,
    darkMode: false,
    analytics: true
};

// conditional-imports.ns
use { FEATURES } from './feature-flags.ns';

let searchModule = null;
if (FEATURES.advancedSearch) {
    searchModule = await import('./advanced-search.ns');
}

let analyticsModule = null;
if (FEATURES.analytics) {
    analyticsModule = await import('./analytics.ns');
}

Module Factories ​

javascript
// module-factory.ns
run createModule(config) {
    return {
        run init() {
            speak.say("Module initialized with config:", config);
        },

        run process(data) {
            return data.map(item => item * config.multiplier);
        }
    };
}

// main.ns
use { createModule } from './module-factory.ns';

let config1 = { multiplier: 2 };
let config2 = { multiplier: 10 };

let module1 = createModule(config1);
let module2 = createModule(config2);

let result1 = module1.process([1, 2, 3]);  // [2, 4, 6]
let result2 = module2.process([1, 2, 3]);  // [10, 20, 30]

Plugin System ​

javascript
// plugin-interface.ns
share run createPlugin(name, handler) {
    return {
        name,
        handler,
        run execute(data) {
            return self.handler(data);
        }
    };
}

// plugins/
// ├── logger.ns
share default createPlugin('logger', (data) => {
    speak.say('Logging:', data);
    return data;
});

// ├── transformer.ns
share default createPlugin('transformer', (data) => {
    return data.toUpperCase();
});

// main.ns
use createPlugin from './plugin-interface.ns';
use loggerPlugin from './plugins/logger.ns';
use transformerPlugin from './plugins/transformer.ns';

let plugins = [loggerPlugin, transformerPlugin];

run processWithPlugins(data) {
    let result = data;

    for (let plugin of plugins) {
        result = plugin.execute(result);
    }

    return result;
}

Module Privacy and Encapsulation ​

Private Members ​

javascript
// counter.ns
let privateCount = 0;  // Private variable

share run increment() {
    privateCount++;
    return privateCount;
}

share run getCount() {
    return privateCount;
}

// Private function (not exported)
run resetCount() {
    privateCount = 0;
}

// main.ns
use { increment, getCount } from './counter.ns';

increment();  // ✅ Public
increment();  // ✅ Public
speak.say(getCount());  // ✅ Public
// resetCount();  // ❌ Private - not accessible

Module Closures ​

javascript
// calculator.ns
share run createCalculator() {
    let history = [];  // Private state

    return {
        run add(a, b) {
            let result = a + b;
            history.push({ operation: 'add', a, b, result });
            return result;
        },

        run getHistory() {
            return [...history];  // Return copy, not reference
        },

        run clearHistory() {
            history = [];
        }
    };
}

// main.ns
use { createCalculator } from './calculator.ns';

let calc = createCalculator();
calc.add(5, 3);      // 8
calc.add(10, 20);    // 30
speak.say(calc.getHistory());  // Shows operation history

Testing Modules ​

Unit Testing ​

javascript
// math.test.ns
use { add, subtract, PI } from './math.ns';

describe('Math Module', () => {
    test('should add two numbers correctly', () => {
        expect(add(2, 3)).toBe(5);
        expect(add(-1, 1)).toBe(0);
        expect(add(0, 0)).toBe(0);
    });

    test('should subtract two numbers correctly', () => {
        expect(subtract(5, 3)).toBe(2);
        expect(subtract(1, 1)).toBe(0);
        expect(subtract(0, 5)).toBe(-5);
    });

    test('should export constants correctly', () => {
        expect(PI).toBe(3.14159);
    });
});

Mocking Modules ​

javascript
// user-service.test.ns
use { UserService } from './user-service.ns';

// Mock the API module
jest.mock('./api.ns', () => ({
    fetchUser: jest.fn(),
    createUser: jest.fn()
}));

use { fetchUser, createUser } from './api.ns';

describe('UserService', () => {
    beforeEach(() => {
        jest.clearAllMocks();
    });

    test('should create user successfully', async () => {
        const mockUser = { id: 1, name: 'Alice', email: 'alice@example.com' };
        createUser.mockResolvedValue(mockUser);

        const userService = new UserService();
        const result = await userService.createUser('Alice', 'alice@example.com');

        expect(result).toEqual(mockUser);
        expect(createUser).toHaveBeenCalledWith('Alice', 'alice@example.com');
    });
});

Performance Optimization ​

Tree Shaking ​

javascript
// utils.ns - Good for tree shaking
share run add(a, b) { return a + b; }
share run subtract(a, b) { return a - b; }
share run multiply(a, b) { return a * b; }
share run divide(a, b) { return a / b; }

// main.ns - Only imports what's needed
use { add, multiply } from './utils.ns';
// subtract and divide are not included in the bundle

Lazy Loading ​

javascript
// lazy-loading.ns
run later loadHeavyModule() {
    let module = hold import('./heavy-module.ns');
    return module;
}

// Only load when needed
run later processData(data) {
    if (data.length > 1000) {
        let heavyModule = hold loadHeavyModule();
        return heavyModule.processLargeDataset(data);
    }

    return processSmallDataset(data);
}

Module Preloading ​

javascript
// Preload critical modules
let criticalModules = [
    import('./auth.ns'),
    import('./core.ns')
];

run later initializeApp() {
    let [authModule, coreModule] = hold Promise.all(criticalModules);

    // Modules are already loaded
    authModule.initialize();
    coreModule.initialize();
}

Next Steps: Practice module organization in the Playground or explore Advanced Examples for complex module patterns.