为了账号安全,请及时绑定邮箱和手机立即绑定

请问该怎样写一个能同时用于 Node 和浏览器的?

/ 猿问

请问该怎样写一个能同时用于 Node 和浏览器的?

RISEBY 2019-09-23 12:16:19

怎样写一个能同时用于 Node 和浏览器的


查看完整描述

5 回答

?
45度呼吸

因此让我们来写一个小的 JavaScript 包,叫做 base64-encode-string。它所做的只是接收一个字符串作为输入,输出其 base64 编码的版本。
对于浏览器来说,这很简单:我们只需要使用自带的 btoa 函数:
module.exports = function (string) {
return btoa(string);
};
然而在 Node 里并没有 btoa 函数。因此,作为替代,我们需要自己创建一个 Buffer,然后在上面调用 buffer.toString():
module.exports = function (string) {
return Buffer.from(string, 'binary').toString('base64');
};
对于一个字符串,这两者都应提供其正确的 base64 编码版本,比如:
var b64encode = require('base64-encode-string');
b64encode('foo'); // Zm9v
b64encode('foobar'); // Zm9vYmFy
现在我们只需要一些方法来检测我们究竟是在浏览器上运行还是在 Node 上,好让我们能保证使用正确的版本。Browserify 和 Webpack 都定义了一个叫 process.browser 的字段,它会返回 true(译者注:即浏览器环境下),然而在 Node 上这个字段返回 false。所以我们只需要简单地:
if (process.browser) {
module.exports = function (string) {
return btoa(string);
};
} else {
module.exports = function (string) {
return Buffer.from(string, 'binary').toString('base64');
};
}
现在我们只需要把我们的文件命名为 index.js,键入 npm publish,我们就完成了,对不对?好的吧,这个方法有效,但不幸的是,这种实现有一个巨大的性能问题。
因为我们的 index.js 文件包含了对 Node 自带的 process 和 Buffer 模块的引用,Browserify 和 Webpack 都会自动引入 其 polyfill,来将它们打包进这些模块。
对于这个简单的九行模块,我算了一下, Browserify 和 Webpack 会创建 一个压缩后有 24.7KB 的包 (7.6KB min+gz)。对于这种东西,用掉的空间实在是太多,因为在浏览器里,只需要 btoa 就能表示这个。
“browser” 字段,我该如何爱你
如果你在 Browserify 或者 Webpack 文档里找解决这个问题的提示,你可能最后会发现 node-browser-resolve。这是一个对于 package.json 内 "browser" 字段的规范,可以被用于定义在浏览器版本构建时需要被换掉的东西。
使用这种技术,我们可以将接下来这段加入我们的 package.json:
{
/* ... */
"browser": {
"./index.js": "./browser.js"
}
}
然后将函数分割成两个不同的文件:index.js 和 browser.js:
// index.js
module.exports = function (string) {
return Buffer.from(string, 'binary').toString('base64');
};

// browser.js
module.exports = function (string) {
return btoa(string);
};
有了这次改进以后,Browserify 和 Webpack 会给出 更加合理的包:Browserify 的包压缩后是 511 字节(315 min+gz),Webpack 的包压缩后是 550 字节(297 min+gz)。
当我们将我们的包发布到 npm 时,在 Node 里运行 require('base64-encode-string') 的人将得到 Node 版的代码,在 Browserfy 和 Webpack 里跑的人会得到浏览器版的代码。
对于 Rollup 来说,这就有点复杂了,但也不需要太多额外的工作。Rollup 用户需要使用 rollup-plugin-node-resolve 并在选项里将 browser 设置为 true。
对 jspm 来说,很不幸地,没有对 “browser” 字段的支持,但是 jspm 用户可以通过 require('base64-encode-string/browser') 或者 jspm install npm:base64-encode-string -o "{main:'browser.js'}" 来迂回地解决问题。另一种方法是,包的作者可以在他们的 package.json 里 指定一个 “jspm” 字段。
进阶技巧
这种直接使用的 "browser" 方法可以工作得很好,但是对于大型项目来说,我发现它在 package.json 和代码库间引入了一种尴尬的耦合。比如说,我们的 package.json 会很快长成这样:
{
/* ... */
"browser": {
"./index.js": "./browser.js",
"./widget.js": "./widget-browser.js",
"./doodad.js": "./doodad-browser.js",
/* etc. */
}
}
在这种情况下,任何时候你想要一个适配于浏览器的模块,都需要分别创建两个文件,并且要记住在 "browser" 字段上添加额外行来将它们连接起来。还要注意不能拼错任何东西!
并且,你会发现你在费尽心机地将微小的代码提取到分离的模块里,仅仅是因为你想要避免 if (process.browser) {} 检查。当这些 *-browser.js 文件积累起来的时候,它们会开始让代码库变得很难跳转。
如果这种情况变得实在太笨重了,有一些别的解决方案。我自己的偏好是使用 Rollup 作为构建工具,来自动地将单个代码库分割到不同的 index.js 和 browser.js 文件里。这对于将你提供给用户的代码的解模块化有额外的价值,节省了空间和时间。
要这样做的话,先安装 rollup 和 rollup-plugin-replace,然后定义一个 rollup.config.js 文件:
import replace from 'rollup-plugin-replace';
export default {
entry: 'src/index.js',
format: 'cjs',
plugins: [
replace({ 'process.browser': !!process.env.BROWSER })
]
};
(我们将使用 process.env.BROWSER 作为一种方便地在浏览器构建和 Node 构建间切换的方式。)
接下来,我们可以创建一个带有单个函数的 src/index.js 文件,使用普通的 process.browser 条件:
export default function base64Encode(string) {
if (process.browser) {
return btoa(string);
} else {
return Buffer.from(string, 'binary').toString('base64');
}
}
然后将 prepublish 步骤添加到 package.json 内,来生成文件:
{
/* ... */
"scripts": {
"prepublish": "rollup -c > index.js && BROWSER=true rollup -c >
browser.js"
}
}
生成的文件都相当直白易读:
// index.js
'use strict';

function base64Encode(string) {
{
return Buffer.from(string, 'binary').toString('base64');
}
}

module.exports = base64Encode;

// browser.js
'use strict';

function base64Encode(string) {
{
return btoa(string);
}
}

module.exports = base64Encode;
你将注意到,Rollup 会按需自动地将 process.browser 转换成 true 或者 false,然后去掉那些无用代码。所以在生成的浏览器包里不会有对于 process 或者 Buffer 的引用。
使用这个技巧,在你的代码库里可以有任意个的 process.browser 切换,并且发布的结果是两个小的集中的 index.js 和 browser.js 文件,其中对于 Node 只有 Node 相关的代码,对于浏览器只有浏览器相关的代码。


查看完整回答
反对 回复 2019-09-25
?
幕布斯5086720

第一步,用node输出一个hello world

1

2

3

4

5

6

    var http=require('http'); 

    http.createServer(function(req,res){ 

        var urlPares=url.parse(req.url); 

        var query=querystring.parse(urlPares.query); 

        res.end('hello world'); 

    }).listen(80);

大部分的node教程在这里会告诉你,我们很容易的建立的一个服务器。但是在实际使我们通常使用的是express.(f**k,难道Node必须要用express吗?自己实现一个Web应用框架真的很难吗?)其实并不是。那么既然打算自己写我们首先要知道我们要做哪些事情。 1.路由或者智能路由 2.静态文件输出 3.session/cookie 4.模版渲染 5.数据库处理 6.文件上传第二步,路由路由好高大上的名字,它是干啥的?url对应具体方法就是它该做的事情。 那么我们为什么不让url对应xxx文件的xx方法。 例如:/user/login能不能自动对应到user.js的login方法上。实现起来很难么?其实只需要几句代码   

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

 var fs = require("fs"); 

    module.exports=function(req,res){ 

        var query=req.query; 

        var urlPares=req.urlPares; 

        var pathname=urlPares.pathname; 

        var arr=pathname.split("/"); 

        req.arr=arr; 

        //start 这段代码处理默认行为。可以先忽略 

        if(arr.length==0||arr.length==1){ 

            arr=["","index","index"]; 

        }else if(arr.length==2){ 

            arr.push("index"); 

        } 

        if(arr[1]==""){ 

            arr[1]="index"; 

        } 

        if(arr[2]==""){ 

            arr[2]="index"; 

        } 

        //end 这段代码处理默认行为。可以先忽略 

        if (fs.existsSync(APP_PATH+'/controller/'+arr[1]+'.js')){ 

            var controller=require('./controller/'+arr[1]); 

            if(controller[arr[2]]){ 

                controller[arr[2]](req,res); 

            }else{ 

                res.writeHead(404,{'Content-Type': 'text/plain' }); 

                res.end("你访问的控制器不存在指定方法"); 

            } 

        }else{ 

            res.writeHead(404,{'Content-Type': 'text/plain' }); 

            res.end("你访问的路径不存在"); 

        } 

    }

通过fs判断文件是否存在。然后去require它就行了。APP_PATH是个全局变量表示程序入口的路径。第三步,静态文件输出静态文件输出我们需要一个库MIME  

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

  var url = require("url"); 

    var fs = require("fs"); 

    var mime = require('mime'); 

    /** 

     * [[检测是否为静态资源]] 

     * @param   {Object}   req [[Description]] 

     * @param   {[[Type]]} res [[Description]] 

     * @returns {bool} [[Description]] 

     */ 

    module.exports = function (req, res) { 

        //正则表达式检测文件后缀 

        var url_resource_reg = /.*\.(html|htm|gif|jpg|jpeg|bmp|webp|htc|swf|png|ico|txt|js|css)/; 

        if (!url_resource_reg.test(req.url)) { 

            return false; 

        } 

        var urlPares = url.parse(req.url); 

        var pathname = urlPares.pathname; 

        var fileUrl = APP_PATH + "/static" + pathname; 

        if (fs.existsSync(fileUrl)) { 

            var contentType = mime.lookup(fileUrl); 

            res.setHeader('Content-Type', contentType || "text/plain"); 

            var fileStream = fs.createReadStream(fileUrl); 

            fileStream.pipe(res); 

            fileStream.on('end', function () { 

                res.end(); 

            }); 

            return true; 

        } else { 

            return false; 

        } 

    } 

第四步,session/cookie

这里稍微有点。但是代码量也不多

    var sessions = {}; 

    var sessionKey = 'session_key'; 

    var EXPIRES = 30 * 60 * 1000; 

    function randString(size) { 

        var result = ''; 

        var allChar = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 

        size = size || 1; 

        while (size--) { 

            result += allChar.charAt(rand(0, allChar.length - 1)); 

        } 

        return result; 

    } 

    var generate = function () { 

        var session = {}; 

        session.id = Date.now() + randString(12); 

        session.cookies = { 

            expire: Date.now() + EXPIRES 

        } 

        sessions[session.id] = session; 

        return session; 

    } 

    var parseCookie= function (cookie) { 

        var cookies = {}; 

        if (!cookie) { 

            return cookies; 

        } 

        var list = cookie.split(";"); 

        for (var i = 0; i < list.length; i++) { 

            var pair = list[i].split("="); 

            cookies[pair[0].trim()] = pair[1]; 

        } 

        return cookies; 

    } 

    var serializeCookies = function (cookies) { 

        var arr = []; 

        for (var key in cookies) { 

            arr.push(serialize(key, cookies[key])); 

        } 

        return arr; 

    } 

    var serialize = function (name, value, option) { 

        var pairs = [name + '=' + encodeURI(value)]; 

        //设置cookie默认共用"/"路径 

        option = option || { 

            path: "/" 

        }; 

        if (option.maxAge) pairs.push('Max-Age=' + option.maxAge); 

        if (option.domain) pairs.push('Domain=' + option.domain); 

        if (option.path) pairs.push('Path=' + option.path); 

        if (option.expires) pairs.push('Expires=' + option.expires); 

        if (option.httpOnly) pairs.push('HttpOnly'); 

        if (option.secure) pairs.push('Secure'); 

        return pairs.join('; '); 

    } 

    module.exports = function (req, res) { 

        req.cookies = parseCookie(req.headers.cookie); 

        var id = req.cookies[sessionKey]; 

        if (!id) { 

            req.session = generate(); 

        } else { 

            var session = sessions[id]; 

            if (session) { 

                if (session.cookies.expire > Date.now()) { 

                    session.cookies.expire = Date.now() + EXPIRES; 

                    req.session = session; 

                } else { 

                    delete sessions[id]; 

                    req.session = generate(); 

                } 

            } else { 

                req.session = generate(); 

            } 

        } 

        for (var key in sessions) { 

            if (sessions[key].cookies.expire < Date.now()) { 

                delete sessions[key]; 

            } 

        } 

        var writeHead = res.writeHead; 

        res.writeHead = function () { 

            delete req.cookies[ham_sessionKey]; 

            var sessionStr = serialize(ham_sessionKey, req.session.id); 

            res.setHeader('Set-Cookie', serializeCookies(req.cookies).concat(sessionStr)); 

            return writeHead.apply(res, arguments); 

        } 

    }

第五步,模版渲染

这是最简单的。

第六步,数据库处理

这里可以是用一些ORM框架。

第七步,文件上传,post

第八步,就是你把上面的代码组织起来。



查看完整回答
反对 回复 2019-09-25
?
翻翻过去那场雪

首先安装express模块,cd到文件夹中,使用npm install express命令安装express module后,会发现文件夹中多了node_modules目录,里边会有express模块了。
进入到任意一个文件夹,执行express app命令,就会创建一个app的应用项目,结构如下:
E:\nodejs\express_demo>express app

create : app
create : app/package.json
create : app/app.js
create : app/public
create : app/bin
create : app/bin/www
create : app/public/stylesheets
create : app/public/stylesheets/style.css
create : app/views
create : app/views/index.jade
create : app/views/layout.jade
create : app/views/error.jade
create : app/public/images
create : app/routes
create : app/routes/index.js
create : app/routes/users.js
create : app/public/javascripts

install dependencies: (这里指示安装必备的包)
$ cd app && npm install

run the app: (这里指示执行,使用npm start)
$ DEBUG=app ./bin/www

cd进入app文件夹中,执行app,使用命令node app这时候会报错,因为第一次使用express框架的话,缺少很多必备的modules
报错:

module.js:340
throw err;
^
Error: Cannot find module 'serve-favicon'//表示缺少serve-favicon模块
at Function.Module._resolveFilename (module.js:338:15)
at Function.Module._load (module.js:280:25)
at Module.require (module.js:364:17)
at require (module.js:380:17)
at Object.<anonymous> (E:\nodejs\Node.js寮€鍙戝疄鎴榎chapter_two\express_dem
o\app\app.js:3:15)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)

这个时候根据提示,安装必备的modules就可以了,如图
Your environment has been set up for using Node.js 0.10.26 (ia32) and npm.

C:\Users\Administrator>e:

E:\>cd nodejs

E:\nodejs>npm install serve-favicon
serve-favicon@2.2.0 node_modules\serve-favicon
├── ms@0.7.0
├── parseurl@1.3.0
├── fresh@0.2.4
└── etag@1.5.1 (crc@3.2.1)

E:\nodejs>npm install morgan
morgan@1.5.1 node_modules\morgan
├── basic-auth@1.0.0
├── depd@1.0.0
├── debug@2.1.1 (ms@0.6.2)
└── on-finished@2.2.0 (ee-first@1.1.0)

E:\nodejs>npm install cookie-parser
cookie-parser@1.3.3 node_modules\cookie-parser
└── cookie-signature@1.0.5

E:\nodejs>npm install body-parser
body-parser@1.12.0 node_modules\body-parser
├── content-type@1.0.1
├── raw-body@1.3.3
├── bytes@1.0.0
├── depd@1.0.0
├── qs@2.3.3
├── iconv-lite@0.4.7
├── on-finished@2.2.0 (ee-first@1.1.0)
├── debug@2.1.1 (ms@0.6.2)
└── type-is@1.6.0 (media-typer@0.3.0, mime-types@2.0.9)

E:\nodejs>

安装完成所有必备的modules后,在此执行app,项目根目录下npm start命令,如果还是提示类似Error: Cannot find module 'jade'错误的话,继续安装必备的模块

6
以上所有的一切完成后,在浏览器下输入http://127.0.0.1:3000/,如下图就表示成功了。





查看完整回答
反对 回复 2019-09-25
?
慕田峪9158850
  • Node.js里的Web框架分为API框架和Web应用框架。前者能够开发出RESTful的API,后者也能开发出RESTful API,但还包括模板、渲染等为前端所准备的功能。

  • API框架的使用场景是为跨平台应用提供统一的数据模型,而渲染由前端/客户端自行解决。目前比较知名的API框架有

  1. restify(文档、Github、NPM);

  2. ActionHero.js(官网、Github、NPM);

  3. LoopBack(官网、Github、NPM);

  4. Frisby(官网、Github、NPM);

  5. Fortune.js(官网、Github、NPM);



查看完整回答
反对 回复 2019-09-25
?
尚方宝剑之说


node.js是使用javascript语言实现服务器的技术,你说的javascript写nodejs是什么意思?
nodejs作为web服务器向浏览器客户端输出页面,原理是和jsp、asp一样的,你输出的页面自然也可以有javascript,这些javascript是在浏览器端执行,要和服务器端node.js区分开,虽然是同一种语言


查看完整回答
反对 回复 2019-09-25

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信