192 lines
4.6 KiB
Go
192 lines
4.6 KiB
Go
// Copyright 2014 dong<ddliuhb@gmail.com>.
|
|
// Licensed under the MIT license.
|
|
//
|
|
// Motto - Modular Javascript environment.
|
|
package motto
|
|
|
|
import (
|
|
"errors"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/robertkrimen/otto"
|
|
)
|
|
|
|
// ModuleLoader is declared to load a module.
|
|
type ModuleLoader func(*Motto) (otto.Value, error)
|
|
|
|
// Create module loader from javascript source code.
|
|
//
|
|
// When the loader is called, the javascript source is executed in Motto.
|
|
//
|
|
// "pwd" indicates current working directory, which might be used to search for
|
|
// modules.
|
|
func CreateLoaderFromSource(source, filename string) ModuleLoader {
|
|
pwd := filepath.Dir(filename)
|
|
return func(vm *Motto) (otto.Value, error) {
|
|
// Wraps the source to create a module environment
|
|
source = "(function(module) {var require = module.require;var exports = module.exports;var __dirname = module.__dirname;" + source + "\n})"
|
|
|
|
// Provide the "require" method in the module scope.
|
|
jsRequire := func(call otto.FunctionCall) otto.Value {
|
|
jsModuleName := call.Argument(0).String()
|
|
|
|
moduleValue, err := vm.Require(jsModuleName, pwd)
|
|
if err != nil {
|
|
jsException(vm, "Error", "motto: "+err.Error())
|
|
}
|
|
|
|
return moduleValue
|
|
}
|
|
|
|
jsModule, _ := vm.Object(`({exports: {}})`)
|
|
jsModule.Set("require", jsRequire)
|
|
jsModule.Set("__dirname", pwd)
|
|
jsExports, _ := jsModule.Get("exports")
|
|
|
|
// Run the module source, with "jsModule" as the "module" variable, "jsExports" as "this"(Nodejs capable).
|
|
var src *otto.Script
|
|
var err error
|
|
var sourceMap string
|
|
if vm.SourceMapEnabled {
|
|
sourceMapFilename := filename + ".map"
|
|
if ok, _ := isFile(sourceMapFilename); ok {
|
|
if content, err := ioutil.ReadFile(sourceMapFilename); err == nil {
|
|
sourceMap = string(content)
|
|
}
|
|
}
|
|
}
|
|
if sourceMap != "" {
|
|
src, err = vm.CompileWithSourceMap(filename, source, sourceMap)
|
|
} else {
|
|
src, err = vm.Compile(filename, source)
|
|
}
|
|
if err != nil {
|
|
return otto.UndefinedValue(), err
|
|
}
|
|
srcValue, err := vm.Otto.Run(src)
|
|
if err != nil {
|
|
return otto.UndefinedValue(), err
|
|
}
|
|
moduleReturn, err := srcValue.Call(jsExports, jsModule)
|
|
if err != nil {
|
|
return otto.UndefinedValue(), err
|
|
}
|
|
|
|
var moduleValue otto.Value
|
|
if !moduleReturn.IsUndefined() {
|
|
moduleValue = moduleReturn
|
|
jsModule.Set("exports", moduleValue)
|
|
} else {
|
|
moduleValue, _ = jsModule.Get("exports")
|
|
}
|
|
|
|
return moduleValue, nil
|
|
}
|
|
}
|
|
|
|
// Create module loader from javascript file.
|
|
//
|
|
// Filename can be a javascript file or a json file.
|
|
func CreateLoaderFromFile(filename string) ModuleLoader {
|
|
return func(vm *Motto) (otto.Value, error) {
|
|
source, err := ioutil.ReadFile(filename)
|
|
|
|
if err != nil {
|
|
return otto.UndefinedValue(), err
|
|
}
|
|
|
|
// load json
|
|
if filepath.Ext(filename) == ".json" {
|
|
return vm.Call("JSON.parse", nil, string(source))
|
|
}
|
|
|
|
return CreateLoaderFromSource(string(source), filename)(vm)
|
|
}
|
|
}
|
|
|
|
// Find a file module by name.
|
|
//
|
|
// If name starts with "." or "/", we search the module in the according locations
|
|
// (name and name.js and name.json).
|
|
//
|
|
// Otherwise we search the module in the "node_modules" sub-directory of "pwd" and
|
|
// "paths"
|
|
//
|
|
// It basicly follows the rules of Node.js module api: http://nodejs.org/api/modules.html
|
|
func FindFileModule(name, pwd string, paths []string) (string, error) {
|
|
if len(name) == 0 {
|
|
return "", errors.New("Empty module name")
|
|
}
|
|
|
|
add := func(choices []string, name string) []string {
|
|
ext := filepath.Ext(name)
|
|
if ext != ".js" && ext != ".json" {
|
|
choices = append(choices, name+".js", name+".json")
|
|
}
|
|
choices = append(choices, name)
|
|
return choices
|
|
}
|
|
|
|
var choices []string
|
|
if name[0] == '.' || filepath.IsAbs(name) {
|
|
if name[0] == '.' {
|
|
name = filepath.Join(pwd, name)
|
|
}
|
|
|
|
choices = add(choices, name)
|
|
} else {
|
|
if pwd != "" {
|
|
choices = add(choices, filepath.Join(pwd, "node_modules", name))
|
|
}
|
|
|
|
for _, v := range paths {
|
|
choices = add(choices, filepath.Join(v, name))
|
|
}
|
|
}
|
|
|
|
for _, v := range choices {
|
|
ok, err := isDir(v)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if ok {
|
|
packageJsonFilename := filepath.Join(v, "package.json")
|
|
ok, err := isFile(packageJsonFilename)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var entryPoint string
|
|
if ok {
|
|
entryPoint, err = parsePackageEntryPoint(packageJsonFilename)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
if entryPoint == "" {
|
|
entryPoint = "./index.js"
|
|
}
|
|
|
|
if !strings.HasPrefix(entryPoint, ".") {
|
|
entryPoint = "./" + entryPoint
|
|
}
|
|
return FindFileModule(entryPoint, v, paths)
|
|
}
|
|
|
|
ok, err = isFile(v)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if ok {
|
|
return filepath.Abs(v)
|
|
}
|
|
}
|
|
|
|
return "", errors.New("Module not found: " + name)
|
|
}
|