Fork me on GitHub
邵金东的个人博客

感谢关注


  • 首页

  • 归档

vue-cli的webpack模板项目配置文件分析

发表于 2018-04-04

目录(?)[-]

一文件结构
二指令分析
三build文件夹分析
builddev-serverjs
buildwebpackbaseconfjs
buildwebpackdevconfjs
buildutilsjs
buildvue-loaderconfjs
builddev-clientjs
buildbuildjs
buildwebpackprodconfjs
buildcheck-versionsjs
四config文件夹分析
configindexjs
configdevenvjsconfigprodenvjs和configtestenvjs
五总结
2017-09-11更新:更新到webpack 2.6.1所对应的配置,完善部分代码注释。

由于最近在vue-cli生成的webpack模板项目的基础上写一个小东西,开发过程中需要改动到build和config里面一些相关的配置,所以刚好趁此机会将所有配置文件看一遍,理一理思路,也便于以后修改配置的时候不会“太折腾”。

一、文件结构

本文主要分析开发(dev)和构建(build)两个过程涉及到的文件,故下面文件结构仅列出相应的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
├─build
│ ├─build.js
│ ├─check-versions.js
│ ├─dev-client.js
│ ├─dev-server.js
│ ├─utils.js
│ ├─vue-loader.conf.js
│ ├─webpack.base.conf.js
│ ├─webpack.dev.conf.js
│ ├─webpack.prod.conf.js
│ └─webpack.test.conf.js
├─config
│ ├─dev.env.js
│ ├─index.js
│ ├─prod.env.js
│ └─test.env.js
├─...
└─package.json

二、指令分析

首先看package.json里面的scripts字段,

1
2
3
4
5
6
7
8
"scripts": {
"dev": "node build/dev-server.js",
"build": "node build/build.js",
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
}

测试的东西先不看,直接看”dev”和”build”。运行”npm run dev”的时候执行的是build/dev-server.js文件,运行”npm run build”的时候执行的是build/build.js文件,我们可以从这两个文件开始进行代码阅读分析。

三、build文件夹分析

build/dev-server.js
首先来看执行”npm run dev”时候最先执行的build/dev-server.js文件。该文件主要完成下面几件事情:

检查node和npm的版本、引入相关插件和配置
webpack对源码进行编译打包并返回compiler对象
创建express服务器
配置开发中间件(webpack-dev-middleware)和热重载中间件(webpack-hot-middleware)
挂载代理服务和中间件
配置静态资源
启动服务器监听特定端口(8080)
自动打开浏览器并打开特定网址(localhost:8080)
说明: express服务器提供静态文件服务,不过它还使用了http-proxy-middleware,一个http请求代理的中间件。前端开发过程中需要使用到后台的API的话,可以通过配置proxyTable来将相应的后台请求代理到专用的API服务器。

详情请看代码注释:

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
// 检查NodeJS和npm的版本
require('./check-versions')()
// 获取基本配置
var config = require('../config')
// 如果Node的环境变量中没有设置当前的环境(NODE_ENV),则使用config中的dev环境配置作为当前的环境
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
// opn是一个可以调用默认软件打开网址、图片、文件等内容的插件
// 这里用它来调用默认浏览器打开dev-server监听的端口,例如:localhost:8080
var opn = require('opn')
var path = require('path')
var express = require('express')
var webpack = require('webpack')
// http-proxy-middleware是一个express中间件,用于将http请求代理到其他服务器
// 例:localhost:8080/api/xxx --> localhost:3000/api/xxx
// 这里使用该插件可以将前端开发中涉及到的请求代理到提供服务的后台服务器上,方便与服务器对接
var proxyMiddleware = require('http-proxy-middleware')
// 开发环境下的webpack配置
var webpackConfig = require('./webpack.dev.conf')
// dev-server 监听的端口,如果没有在命令行传入端口号,则使用config.dev.port设置的端口,例如8080
var port = process.env.PORT || config.dev.port
// 用于判断是否要自动打开浏览器的布尔变量,当配置文件中没有设置自动打开浏览器的时候其值为 false
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// HTTP代理表,指定规则,将某些API请求代理到相应的服务器
var proxyTable = config.dev.proxyTable
// 创建express服务器
var app = express()
// webpack根据配置开始编译打包源码并返回compiler对象
var compiler = webpack(webpackConfig)
// webpack-dev-middleware将webpack编译打包后得到的产品文件存放在内存中而没有写进磁盘
// 将这个中间件挂到express上使用之后即可提供这些编译后的产品文件服务
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath, // 设置访问路径为webpack配置中的output里面所对应的路径
quiet: true // 设置为true,使其不要在控制台输出日志
})
// webpack-hot-middleware,用于实现热重载功能的中间件
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: false, // 关闭控制台的日志输出
heartbeat: 2000 // 发送心跳包的频率
})
// webpack(重新)编译打包完成后并将js、css等文件inject到html文件之后,通过热重载中间件强制页面刷新
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// 根据 proxyTable 中的代理请求配置来设置express服务器的http代理规则
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
// 格式化options,例如将'www.example.com'变成{ target: 'www.example.com' }
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(options.filter || context, options))
})
// handle fallback for HTML5 history API
// 重定向不存在的URL,用于支持SPA(单页应用)
// 例如使用vue-router并开启了history模式
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
// 挂载webpack-dev-middleware中间件,提供webpack编译打包后的产品文件服务
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
// 挂载热重载中间件
app.use(hotMiddleware)
// serve pure static assets
// 提供static文件夹上的静态文件服务
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
// 访问链接
var uri = 'http://localhost:' + port
// 创建promise,在应用服务启动之后resolve
// 便于外部文件require了这个dev-server之后的代码编写
var _resolve
var readyPromise = new Promise(resolve => {
_resolve = resolve
})
console.log('> Starting dev server...')
// webpack-dev-middleware等待webpack完成所有编译打包之后输出提示语到控制台,表明服务正式启动
// 服务正式启动才自动打开浏览器进入页面
devMiddleware.waitUntilValid(() => {
console.log('> Listening at ' + uri + '\n')
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
_resolve()
})
// 启动express服务器并监听相应的端口
var server = app.listen(port)
// 暴露本模块的功能给外部使用,例如下面这种用法
// var devServer = require('./build/dev-server')
// devServer.ready.then(() => {...})
// if (...) { devServer.close() }
module.exports = {
ready: readyPromise,
close: () => {
server.close()
}
}

build/webpack.base.conf.js
从代码中看到,dev-server使用的webpack配置来自build/webpack.dev.conf.js文件(测试环境下使用的是build/webpack.prod.conf.js,这里暂时不考虑测试环境)。而build/webpack.dev.conf.js中又引用了webpack.base.conf.js,所以这里我先分析webpack.base.conf.js。

webpack.base.conf.js主要完成了下面这些事情:

配置webpack编译入口
配置webpack输出路径和命名规则
配置模块resolve规则
配置不同类型模块的处理规则
说明: 这个配置里面只配置了.js、.vue、图片、字体等几类文件的处理规则,如果需要处理其他文件可以在module.rules里面另行配置。

具体请看代码注释:

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
var path = require('path')
var fs = require('fs')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')
// 获取绝对路径
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
// webpack入口文件
entry: {
app: './src/main.js'
},
// webpack输出路径和命名规则
output: {
// webpack输出的目标文件夹路径(例如:/dist)
path: config.build.assetsRoot,
// webpack输出bundle文件命名格式
filename: '[name].js',
// webpack编译输出的发布路径(例如'//cdn.xxx.com/app/')
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
// 模块resolve的规则
resolve: {
extensions: ['.js', '.vue', '.json'],
// 别名,方便引用模块,例如有了别名之后,
// import Vue from 'vue/dist/vue.common.js'可以写成 import Vue from 'vue'
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
},
symlinks: false
},
// 不同类型模块的处理规则
module: {
rules: [
{// 对src和test文件夹下的.js和.vue文件使用eslint-loader进行代码规范检查
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter')
}
},
{// 对所有.vue文件使用vue-loader进行编译
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{// 对src和test文件夹下的.js文件使用babel-loader将es6+的代码转成es5
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{// 对图片资源文件使用url-loader
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
// 小于10K的图片转成base64编码的dataURL字符串写到代码中
limit: 10000,
// 其他的图片转移到静态资源文件夹
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{// 对多媒体资源文件使用url-loader
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
// 小于10K的资源转成base64编码的dataURL字符串写到代码中
limit: 10000,
// 其他的资源转移到静态资源文件夹
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{// 对字体资源文件使用url-loader
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
// 小于10K的资源转成base64编码的dataURL字符串写到代码中
limit: 10000,
// 其他的资源转移到静态资源文件夹
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}

build/webpack.dev.conf.js
接下来看webpack.dev.conf.js,这里面在webpack.base.conf的基础上增加完善了开发环境下面的配置,主要包括下面几件事情:

将webpack的热重载客户端代码添加到每个entry对应的应用
合并基础的webpack配置
配置样式文件的处理规则,styleLoaders
配置Source Maps
配置webpack插件
详情请看代码注释:

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
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
// webpack-merge是一个可以合并数组和对象的插件
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
// html-webpack-plugin用于将webpack编译打包后的产品文件注入到html模板中
// 即自动在index.html里面加上<link>和<script>标签引用webpack打包后的文件
var HtmlWebpackPlugin = require('html-webpack-plugin')
// friendly-errors-webpack-plugin用于更友好地输出webpack的警告、错误等信息
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// add hot-reload related code to entry chunks
// 给每个入口页面(应用)加上dev-client,用于跟dev-server的热重载插件通信,实现热更新
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
module: {
// 样式文件的处理规则,对css/sass/scss等不同内容使用相应的styleLoaders
// 由utils配置出各种类型的预处理语言所需要使用的loader,例如sass需要使用sass-loader
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// cheap-module-eval-source-map is faster for development
// 使用这种source-map更快
devtool: '#cheap-module-eval-source-map',
// webpack插件
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// 开启webpack热更新功能
new webpack.HotModuleReplacementPlugin(),
// webpack编译过程中出错的时候跳过报错阶段,不会阻塞编译,在编译结束后报错
new webpack.NoEmitOnErrorsPlugin(),
// 自动将依赖注入html模板,并输出最终的html文件到目标文件夹
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
new FriendlyErrorsPlugin()
]
})

build/utils.js
utils提供工具函数,包括生成处理各种样式语言的loader,获取资源文件存放路径的工具函数。

  1. 计算资源文件存放路径
  2. 生成cssLoaders用于加载.vue文件中的样式
  3. 生成styleLoaders用于加载不在.vue文件中的单独存在的样式文件
    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
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    var path = require('path')
    var config = require('../config')
    // extract-text-webpack-plugin可以提取bundle中的特定文本,将提取后的文本单独存放到另外的文件
    // 这里用来提取css样式
    var ExtractTextPlugin = require('extract-text-webpack-plugin')
    // 资源文件的存放路径
    exports.assetsPath = function (_path) {
    var assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
    return path.posix.join(assetsSubDirectory, _path)
    }
    // 生成css、sass、scss等各种用来编写样式的语言所对应的loader配置
    exports.cssLoaders = function (options) {
    options = options || {}
    // css-loader配置
    var cssLoader = {
    loader: 'css-loader',
    options: {
    // 是否最小化
    minimize: process.env.NODE_ENV === 'production',
    // 是否使用source-map
    sourceMap: options.sourceMap
    }
    }
    // generate loader string to be used with extract text plugin
    // 生成各种loader配置,并且配置了extract-text-pulgin
    function generateLoaders (loader, loaderOptions) {
    // 默认是css-loader
    var loaders = [cssLoader]
    // 如果非css,则增加一个处理预编译语言的loader并设好相关配置属性
    // 例如generateLoaders('less'),这里就会push一个less-loader
    // less-loader先将less编译成css,然后再由css-loader去处理css
    // 其他sass、scss等语言也是一样的过程
    if (loader) {
    loaders.push({
    loader: loader + '-loader',
    options: Object.assign({}, loaderOptions, {
    sourceMap: options.sourceMap
    })
    })
    }
    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
    // 配置extract-text-plugin提取样式
    return ExtractTextPlugin.extract({
    use: loaders,
    fallback: 'vue-style-loader'
    })
    } else {
    // 无需提取样式则简单使用vue-style-loader配合各种样式loader去处理<style>里面的样式
    return ['vue-style-loader'].concat(loaders)
    }
    }
    // https://vue-loader.vuejs.org/en/configurations/extract-css.html
    // 得到各种不同处理样式的语言所对应的loader
    return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
    }
    }
    // Generate loaders for standalone style files (outside of .vue)
    // 生成处理单独的.css、.sass、.scss等样式文件的规则
    exports.styleLoaders = function (options) {
    var output = []
    var loaders = exports.cssLoaders(options)
    for (var extension in loaders) {
    var loader = loaders[extension]
    output.push({
    test: new RegExp('\\.' + extension + '$'),
    use: loader
    })
    }
    return output
    }
    build/vue-loader.conf.js
    vue-loader.conf的配置比较简单,详情请看代码注释:
    var utils = require('./utils')
    var config = require('../config')
    var isProduction = process.env.NODE_ENV === 'production'
    module.exports = {
    // 处理.vue文件中的样式
    loaders: utils.cssLoaders({
    // 是否打开source-map
    sourceMap: isProduction
    ? config.build.productionSourceMap
    : config.dev.cssSourceMap,
    // 是否提取样式到单独的文件
    extract: isProduction
    }),
    transformToRequire: {
    video: 'src',
    source: 'src',
    img: 'src',
    image: 'xlink:href'
    }
    }
    build/dev-client.js
    dev-client.js里面主要写了浏览器端代码,用于实现webpack的热更新。
    /* eslint-disable */
    // 实现浏览器端的EventSource,用于跟服务器双向通信
    // webpack热重载客户端跟dev-server上的热重载插件之间需要进行双向通信
    // 服务端webpack重新编译后,会向客户端推送信息,告诉客户端进行更新
    require('eventsource-polyfill')
    // webpack热重载客户端
    var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
    // 客户端收到更新动作,执行页面刷新
    hotClient.subscribe(function (event) {
    if (event.action === 'reload') {
    window.location.reload()
    }
    })

build/build.js
讲完了开发环境下的配置,下面开始来看构建环境下的配置。执行”npm run build”的时候首先执行的是build/build.js文件,build.js主要完成下面几件事:

loading动画
删除目标文件夹
执行webpack构建
输出信息
说明: webpack编译之后会输出到配置里面指定的目标文件夹;删除目标文件夹之后再创建是为了去除旧的内容,以免产生不可预测的影响。

详情请看代码注释:

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
// 检查NodeJS和npm的版本
require('./check-versions')()
process.env.NODE_ENV = 'production'
// ora,一个可以在终端显示spinner的插件
var ora = require('ora')
// rm,用于删除文件或文件夹的插件
var rm = require('rimraf')
var path = require('path')
// chalk,用于在控制台输出带颜色字体的插件
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for production...')
spinner.start() // 开启loading动画
// 首先将整个dist文件夹以及里面的内容删除,以免遗留旧的没用的文件
// 删除完成后才开始webpack构建打包
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
// 执行webpack构建打包,完成之后在终端输出构建完成的相关信息或者输出报错信息并退出程序
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})

build/webpack.prod.conf.js
构建的时候用到的webpack配置来自webpack.prod.conf.js,该配置同样是在webpack.base.conf基础上的进一步完善。主要完成下面几件事情:

合并基础的webpack配置
配置样式文件的处理规则,styleLoaders
配置webpack的输出
配置webpack插件
gzip模式下的webpack插件配置
webpack-bundle分析
说明: webpack插件里面多了丑化压缩代码以及抽离css文件等插件。

详情请看代码:

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
// copy-webpack-plugin,用于将static中的静态文件复制到产品文件夹dist
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
// optimize-css-assets-webpack-plugin,用于优化和最小化css资源
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
// 样式文件的处理规则,对css/sass/scss等不同内容使用相应的styleLoaders
// 由utils配置出各种类型的预处理语言所需要使用的loader,例如sass需要使用sass-loader
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
// 是否使用source-map
devtool: config.build.productionSourceMap ? '#source-map' : false,
// webpack输出路径和命名规则
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
// webpack插件
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
// 丑化压缩JS代码
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// extract css into its own file
// 将css提取到单独的文件
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
// 优化、最小化css代码,如果只简单使用extract-text-plugin可能会造成css重复
// 具体原因可以看npm上面optimize-css-assets-webpack-plugin的介绍
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
// 将产品文件的引用注入到index.html
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
// 删除index.html中的注释
removeComments: true,
// 删除index.html中的空格
collapseWhitespace: true,
// 删除各种html标签属性值的双引号
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
// 注入依赖的时候按照依赖先后顺序进行注入,比如,需要先注入vendor.js,再注入app.js
chunksSortMode: 'dependency'
}),
// keep module.id stable when vender modules does not change
new webpack.HashedModuleIdsPlugin(),
// split vendor js into its own file
// 将所有从node_modules中引入的js提取到vendor.js,即抽取库文件
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
// 从vendor中提取出manifest,原因如上
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
// copy custom static assets
// 将static文件夹里面的静态资源复制到dist/static
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
// 如果开启了产品gzip压缩,则利用插件将构建后的产品文件进行压缩
if (config.build.productionGzip) {
// 一个用于压缩的webpack插件
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
// 压缩算法
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
// 如果启动了report,则通过插件给出webpack构建打包后的产品文件分析报告
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
build/check-versions.js
最后是build文件夹下的check-version.js,它完成对node和npm的版本检测,下面是其代码注释:
// chalk, 用于在控制台输出带颜色字体的插件
var chalk = require('chalk')
// semver, 语义化版本检查插件(The semantic version parser used by npm)
var semver = require('semver')
var packageConfig = require('../package.json')
// shelljs, 执行Unix命令行的插件
var shell = require('shelljs')
// 开辟子进程执行指令cmd并返回结果
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
// node和npm版本需求
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
var warnings = []
// 依次判断版本是否符合要求
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
// 如果有警告则将其输出到控制台
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

四、config文件夹分析

config/index.js
config文件夹下最主要的文件就是index.js了,在这里面描述了开发和构建两种环境下的配置,前面的build文件夹下也有不少文件引用了index.js里面的配置。下面是代码注释:

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
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
// 构建产品时使用的配置
build: {
// 环境变量
env: require('./prod.env'),
// html入口文件
index: path.resolve(__dirname, '../dist/index.html'),
// 产品文件的存放路径
assetsRoot: path.resolve(__dirname, '../dist'),
// 二级目录,存放静态资源文件的目录,位于dist文件夹下
assetsSubDirectory: 'static',
// 发布路径,如果构建后的产品文件有用于发布CDN或者放到其他域名的服务器,可以在这里进行设置
// 设置之后构建的产品文件在注入到index.html中的时候就会带上这里的发布路径
assetsPublicPath: '/',
// 是否使用source-map
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
// 是否开启gzip压缩
productionGzip: false,
// gzip模式下需要压缩的文件的扩展名,设置js、css之后就只会对js和css文件进行压缩
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
// 是否展示webpack构建打包之后的分析报告
bundleAnalyzerReport: process.env.npm_config_report
},
// 开发过程中使用的配置
dev: {
// 环境变量
env: require('./dev.env'),
// dev-server监听的端口
port: 8080,
// 是否自动打开浏览器
autoOpenBrowser: true,
// 静态资源文件夹
assetsSubDirectory: 'static',
// 发布路径
assetsPublicPath: '/',
// 代理配置表,在这里可以配置特定的请求代理到对应的API接口
// 例如将'localhost:8080/api/xxx'代理到'www.example.com/api/xxx'
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
// 是否开启 cssSourceMap
cssSourceMap: false
}
}

config/dev.env.js、config/prod.env.js和config/test.env.js
这三个文件就简单设置了环境变量而已,没什么特别的。

五、总结

到这里对模板项目的build和config文件夹下面的内容已经基本了解,知道了在实际使用中根据自己的需求修改哪里的配置,例如,当我有需要配置代理的时候要在config/index.js里面的dev.proxyTable设置,当我修改了资源文件夹名称static同样需要在config/index.js里面设置。webpack的插件好多,在看代码的过程中遇到不认识的插件都是要去查看很多文档(github,npm或者博客),感觉实际过程中更改插件配置或者使用新插件也是需要费点心思钻文档和网上其他博客介绍

判断浏览器版本

发表于 2018-04-03

代码如下

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
function userBrowser(){
var browserName=navigator.userAgent.toLowerCase();
if(/msie/i.test(browserName) && !/opera/.test(browserName)){
alert("IE");
return ;
}else if(/firefox/i.test(browserName)){
alert("Firefox");
return ;
}else if(/chrome/i.test(browserName) && /webkit/i.test(browserName) && /mozilla/i.test(browserName)){
alert("Chrome");
return ;
}else if(/opera/i.test(browserName)){
alert("Opera");
return ;
}else if(/iPad/i){
alert("ipad");
return ;
}
else if(/webkit/i.test(browserName) &&!(/chrome/i.test(browserName) && /webkit/i.test(browserName) && /mozilla/i.test(browserName))){
alert("Safari");
return ;
}else{
alert("unKnow");
}
}
userBrowser();

基于Gulp的前端开发模版

发表于 2018-04-03

项目运行

1
2
3
4
5
6
7
8
9
$ git clone https://github.com/jusfoun-FE/automation-development-ie8.git
$ cd automation-development-ie8
$ npm install
$ npm run dev //用于开发环境
$ npm run build //用于生产环境

浏览器输入地址查看模板:http://localhost:3000/html/index/

项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├─ build/ # gulp 任务配置目录
├─ dist/ # build 生成的生产环境下的项目
├─ src/ # 源码目录(开发都在这里进行)
│ ├── components/ # 组件(COMPONENT)
│ ├── html/ # 页面级html
│ ├── css/ # css文件存放目录(less ,scss,css)
│ ├── js/ # js文件存放目录
│ ├── images/ # 图片文件
├── .babelrc # Babel 转码配置
├── .gitignore # (配置)需被 Git 忽略的文件(夹)
├── gulpfile.js # gulpfile基础配置文件
├── package.json # (这个就不用多解释了吧)

IE低版本兼容小贴士

IE浏览器hack

html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--[if IE]>
这段文字只在IE浏览器显示
<![endif]-->
只在IE6下生效
<!--[if IE 6]>
这段文字只在IE6浏览器显示
<![endif]-->
只在IE6以上版本生效
<!--[if gte IE 6]>
这段文字只在IE6以上(包括)版本IE浏览器显示
<![endif]-->
只在IE8上不生效
<!--[if ! IE 8]>
这段文字在非IE8浏览器显示
<![endif]-->
非IE浏览器生效
<!--[if !IE]>
这段文字只在非IE浏览器显示
<![endif]-->

css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
“-″减号是IE6专有的hack
“\9″ IE6/IE7/IE8/IE9/IE10都生效
“\0″ IE8/IE9/IE10都生效,是IE8/9/10的hack
“\9\0″ 只对IE9/IE10生效,是IE9/10的hack
.hack{
/*demo1 注意顺序,否则IE6/7下可能无法正确显示,导致结果显示为白色背景*/
background-color:red; /* All browsers */
background-color:blue !important;/* All browsers but IE6 */
*background-color:black; /* IE6, IE7 */
+background-color:yellow;/* IE6, IE7*/
background-color:gray\9; /* IE6, IE7, IE8, IE9, IE10 */
background-color:purple\0; /* IE8, IE9, IE10 */
background-color:orange\9\0;/*IE9, IE10*/
_background-color:green; /* Only works in IE6 */
*+background-color:pink; /* WARNING: Only works in IE7 ? Is it right? */
}

1、IE8及以下,布局问题

不使用flex,box-size:border-box,calc

2、ie下进行跨域请求解决方案

引入jquery.xdomainrequest.min.js

3、ie8,9 进行跨域post请求,参数无法传递解决办法

jquery的ajax方法添加
crossDomain: true == !(document.all)
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.ajax({
url: url,
type: 'POST',
data: params,
dataType: 'json',
contentType: "application/json",
crossDomain: true == !(document.all),
success: function (res) {
...
},
error: function (err) {
...
}
})

4、IE6,IE7下overflow:hidden无效

解决办法:position:relative; 或者 position:relative; / for IE6,IE7 */ 即可解决该bug。

既:

1
2
position: relative;
overflow: hidden;

5、css3选择器兼容问题?

1
2
3
4
<!- -[if (gte IE 6)&(lte IE 8)]>
<script type="text/javascript" src="./js/common/nwmatcher.js"></script>
<script type="text/javascript" src="./js/common/selectivizr-min.js"></script>
<![endif]- ->

在非必不可得到情况下不建议使用,会引起页面卡顿

6、兼容IE8不直接使用sessionStorage,localStorage

使用公用方法,使用办法如下:

1
2
3
4
5
6
7
function setKeyItem(key,value) {
if (window.sessionStorage){
sessionStorage.setItem(key,value);
}else{
setCookie(key,value,TimeoutCookie)
}
}

7、“JSON”未定义

引入json2.js

注:本模板已经引入

下载地址:https://github.com/douglascrockford/JSON-js

8、IE跨域下出现{“readyState”:0,”status”:0,”TypeError”…}

对浏览器进行设置 工具->Internet选项->安全->自定义级别->其他->通过域访问数据源->启用

9、透明度兼容性:

a、使用rgba背景色做透明的:

背景色变成:background: #000 \9;

再加IE9及以下的透明度设置方法

1
2
3
opacity=80
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);

b、直接使用opacity设置透明度的

添加IE9及以下的透明度设置方法

1
2
3
opacity=80
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);

10、输入框垂直居中问题

将line-height设置成和height一样 即可,如:

1
2
3
4
innput.txt{
height: 36px;
line-height: 36px;
}

11、布局错位问题:

a、没有清除浮动的清除浮动即可,如:

html:

1
2
3
4
<div class="parent clear">
<div class="child">child1</div>
<div class="child">child2</div>
</div>

css:

1
2
3
4
5
6
7
.clear{
overflow: hidden;
*zoom:1;
}
.parent .child{
float: left;
}

12、css伪类问题:

将用伪类实现的效果换成其他实现方式;

主要是针对IE7内核,如果不用兼容IE7内核,则不用处理此类问题。

13、ul元素中li 两端顶头,间距均等问题

li使用float为left,加上margin-left,
然后ul同时也加上margin-left,其值使用与li相对应的负值,如:

html:

1
2
3
4
5
6
<ul class="parent">
<li class="child">测试</li>
<li class="child">测试</li>
<li class="child">测试</li>
<li class="child">测试</li>
</ul>

css:

1
2
3
4
5
6
7
8
9
10
.parent{
width 1200px;
margin-left: -20px;
*zoom:1;
}
.parent .child{
width 280px;
height:400px;
margin-left: 20px;
}

14、background-size: cover

如果你想使用background-size: cover设置全屏背景,

很遗憾IE8办不到…但可以使用IE独有的AlphaImageLoader滤镜来实现,添加一条如下的CSS样式:

1
2
3
4
.box{
background-size: cover;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale , src='image.img')\9;
}

将sizingMethod设置为scale,src设置成背景图片地址就OK了。

还没完,如果你在此背景之上放置了链接,那这个链接是无法点击的。

一般情况下的解决办法是为链接或按钮添加position:relative使其相对浮动。

15、last-child

first-child是CSS2的内容,但是last-child就不是了,所以IE8不买账。

推荐的做法不是使用last-child,而是给最后一个元素设置一个.last的class,然后对此进行样式设置,这样就全部兼容了。

16、Media Query(媒体查询)

IE8似乎无法识别Media Query,所以需要hack一下啦!推荐采用Respond.js解决此问题。

Respond.js让IE6-8支持CSS3 Media Query。

Bootstrap里面就引入了这个js文件,从名字看出来是自适应的兼容。

使用:考虑到IE9是支持CSS3的,所以直接在HTML页面的head标签中添加脚本引用即可:

1
2
3
<!--[if lt IE 9]>
<script src="http://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
<![endif]—>

官方demo地址:http://scottjehl.github.com/Respond/test/test.html

a.在css中正常用 min/max-width media

b.引入respond.min.js,但要在css的后面

(越早引入越好,在ie下面看到页面闪屏的概率就越低,因为最初css会先渲染出来,如果respond.js加载得很后面,这时重新根据media query解析出来的css会再改变一次页面的布局等,所以看起来有闪屏的现象)

17、关于max-width

a. td中的max-width

如果针对td中的img元素设置max-width: 100%,在IE和Firefox你会发现不奏效,而在Chrome中却是可以的。

经查询发现需要给table设置table-layout: fixed.

b. 嵌套标签中的max-width

如下的HTML结构:

1
2
3
4
5
<div class="work-item">
<a href="#" class="work-link">
<img src="sample.jpg" class="work-image img-responsive">
</a>
</div>

最外层元素.work-item设置了固定宽度,但是对img设置max-width为100%却无效.

后来才发现需要再对a标签设置width: 100%,这样才能使最内层的img标签充满整个div。

18、嵌套inline-block下padding元素重叠

html:

1
2
3
4
5
<ul>
<li><a>1</a></li>
<li><a>2</a></li>
<li><a>3</a></li>
</ul>

CSS:

1
2
3
4
5
6
7
ul li{
display: inline-block;
}
ul li a{
display: inline-block;
padding: 10px 15px;
}

按理来说a标签之间的距离应该是30px,但在IE8中出现了重叠,只有15px。

我的解决方法是使用float: left替代display: inline-block实现水平布局。

19、filter blur

CSS3中提供支持滤镜效果的属性filter,比如支持高斯模糊效果:

1
2
3
filter: blur(10px);
-webkit-filter: blur(10px);
-moz-filter: blur(10px);

IE8对filter: blur(10px)的显示效果是对HTML元素进行小范围的模糊处理,这个效果并不是高斯模糊

要想支持高斯模糊,需要如下设置:

1
filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius='10');

在实践中发现一个坑就是,所有position: relative的元素都不会生效。

其他的发现是,IE9对filter: blur(10px)无效,

而对filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius=’10’),是针对元素小范围的模糊效果。

§ 参考

  • npmjs
  • gulpjs
  • babel

获取一周内,一月内,一年内的开始时间和结束时间

发表于 2018-04-03
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
// 时间格式
function formatDate(now) {
var now=new Date(now);
var year=now.getFullYear();
var month=now.getMonth()+1;
var date=now.getDate();
var hour=now.getHours();
var minute=now.getMinutes();
var second=now.getSeconds();
return year+"-"+addZero(month)+"-"+addZero(date)+" "+addZero(hour)+":"+addZero(minute)+":"+addZero(second);
//时间范围
function durTime(param){
var date = new Date()*1;
var timeStr = {};
//开始时间
timeStr.startTime = formatDate(date);
if(param == "day"){
var endStr = date-24*60*60*1000;
timeStr.endTime = formatDate(endStr);
}else if(param == "week"){
var endStr = date-7*24*60*60*1000;
timeStr.endTime = formatDate(endStr);
}else if(param == "month"){
var endStr = date-30*24*60*60*1000;
timeStr.endTime = formatDate(endStr);
}else if(param == "year"){
var endStr = date-365*24*60*60*1000;
timeStr.endTime = formatDate(endStr);
}
return timeStr
}
// 补零
function addZero(val){
if(val>=10){
return val;
}else{
return "0"+val;
}
}
// 调用
console.log( durTime("day") );
console.log( durTime("week") );
console.log( durTime("month") );
console.log( durTime("year") );

未命名

发表于 2018-04-03

点击页面排除控件让控件隐藏

1
2
3
4
5
6
7
8
9
10
11
12
$(document).click(function(e){
var a = e.target
var b = $("#ztree")[0];
console.log(a,b,$.contains(a,b))
if($.contains(b,a)){
//当点击元素在$("#ztree")里面的时候
return false;
}else{
$("#ztree").hide()
}
});

解析

概述

一个DOM节点是否包含另一个DOM节点。

参数

container,containedObject,ObjectV1.4

container:DOM元素作为容器,可以包含其他元素

contained:DOM节点,可能被其他元素所包含

示例

描述:

检测一个元素是否包含另一个元素

jQuery 代码:

1
2
jQuery.contains(document.documentElement, document.body); // true
jQuery.contains(document.body, document.documentElement); // false

媒体查询

发表于 2018-04-03

媒体查询

设备尺寸参考 :Mobile devices

媒体查询类型浏览器支持情况:CSS3 Media Queries overview

常用查询语句

判断设备横竖屏

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/* 横屏 */
@media all and (orientation :landscape) {
}
/* 竖屏 */
@media all and (orientation :portrait) {
}
判断设备宽高
/* 设备宽度大于 320px 小于 640px */
@media all and (min-width:320px) and (max-width:640px) {
}
判断设备像素比
/* 设备像素比为 1 */
@media only screen and (-webkit-min-device-pixel-ratio: 1), only screen and (min-device-pixel-ratio: 1) {
}
/* 设备像素比为 1.5 */
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {
}
/* 设备像素比为 2 */
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) {
}
常用设备设置
iPhones
/* ----------- iPhone 4 and 4S ----------- */
/* Portrait and Landscape */
@media only screen
and (min-device-width: 320px)
and (max-device-width: 480px)
and (-webkit-min-device-pixel-ratio: 2) {
}
/* Portrait */
@media only screen
and (min-device-width: 320px)
and (max-device-width: 480px)
and (-webkit-min-device-pixel-ratio: 2)
and (orientation: portrait) {
}
/* Landscape */
@media only screen
and (min-device-width: 320px)
and (max-device-width: 480px)
and (-webkit-min-device-pixel-ratio: 2)
and (orientation: landscape) {
}
/* ----------- iPhone 5 and 5S ----------- */
/* Portrait and Landscape */
@media only screen
and (min-device-width: 320px)
and (max-device-width: 568px)
and (-webkit-min-device-pixel-ratio: 2) {
}
/* Portrait */
@media only screen
and (min-device-width: 320px)
and (max-device-width: 568px)
and (-webkit-min-device-pixel-ratio: 2)
and (orientation: portrait) {
}
/* Landscape */
@media only screen
and (min-device-width: 320px)
and (max-device-width: 568px)
and (-webkit-min-device-pixel-ratio: 2)
and (orientation: landscape) {
}
/* ----------- iPhone 6 ----------- */
/* Portrait and Landscape */
@media only screen
and (min-device-width: 375px)
and (max-device-width: 667px)
and (-webkit-min-device-pixel-ratio: 2) {
}
/* Portrait */
@media only screen
and (min-device-width: 375px)
and (max-device-width: 667px)
and (-webkit-min-device-pixel-ratio: 2)
and (orientation: portrait) {
}
/* Landscape */
@media only screen
and (min-device-width: 375px)
and (max-device-width: 667px)
and (-webkit-min-device-pixel-ratio: 2)
and (orientation: landscape) {
}
/* ----------- iPhone 6+ ----------- */
/* Portrait and Landscape */
@media only screen
and (min-device-width: 414px)
and (max-device-width: 736px)
and (-webkit-min-device-pixel-ratio: 3) {
}
/* Portrait */
@media only screen
and (min-device-width: 414px)
and (max-device-width: 736px)
and (-webkit-min-device-pixel-ratio: 3)
and (orientation: portrait) {
}
/* Landscape */
@media only screen
and (min-device-width: 414px)
and (max-device-width: 736px)
and (-webkit-min-device-pixel-ratio: 3)
and (orientation: landscape) {
}
Galaxy Phones
/* ----------- Galaxy S3 ----------- */
/* Portrait and Landscape */
@media screen
and (device-width: 320px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 2) {
}
/* Portrait */
@media screen
and (device-width: 320px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 2)
and (orientation: portrait) {
}
/* Landscape */
@media screen
and (device-width: 320px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 2)
and (orientation: landscape) {
}
/* ----------- Galaxy S4 ----------- */
/* Portrait and Landscape */
@media screen
and (device-width: 320px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 3) {
}
/* Portrait */
@media screen
and (device-width: 320px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: portrait) {
}
/* Landscape */
@media screen
and (device-width: 320px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: landscape) {
}
/* ----------- Galaxy S5 ----------- */
/* Portrait and Landscape */
@media screen
and (device-width: 360px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 3) {
}
/* Portrait */
@media screen
and (device-width: 360px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: portrait) {
}
/* Landscape */
@media screen
and (device-width: 360px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: landscape) {
}
HTC Phones
/* ----------- HTC One ----------- */
/* Portrait and Landscape */
@media screen
and (device-width: 360px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 3) {
}
/* Portrait */
@media screen
and (device-width: 360px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: portrait) {
}
/* Landscape */
@media screen
and (device-width: 360px)
and (device-height: 640px)
and (-webkit-device-pixel-ratio: 3)
and (orientation: landscape) {
}
iPads
/* ----------- iPad mini ----------- */
/* Portrait and Landscape */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (-webkit-min-device-pixel-ratio: 1) {
}
/* Portrait */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (orientation: portrait)
and (-webkit-min-device-pixel-ratio: 1) {
}
/* Landscape */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (orientation: landscape)
and (-webkit-min-device-pixel-ratio: 1) {
}
/* ----------- iPad 1 and 2 ----------- */
/* Portrait and Landscape */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (-webkit-min-device-pixel-ratio: 1) {
}
/* Portrait */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (orientation: portrait)
and (-webkit-min-device-pixel-ratio: 1) {
}
/* Landscape */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (orientation: landscape)
and (-webkit-min-device-pixel-ratio: 1) {
}
/* ----------- iPad 3 and 4 ----------- */
/* Portrait and Landscape */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (-webkit-min-device-pixel-ratio: 2) {
}
/* Portrait */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (orientation: portrait)
and (-webkit-min-device-pixel-ratio: 2) {
}
/* Landscape */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (orientation: landscape)
and (-webkit-min-device-pixel-ratio: 2) {
}

第一次使用vuex

发表于 2018-04-03

首先在main.js里面使用vuex,state里面声明一些储存的值,通过mutations去改变state里面的状态值。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: false
},
mutations: {
changeState (state, payload) {
state.count = payload.val
}
}
})
//需要把store放在vue里面
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})

然后在登陆页面修改store状态值由于vuex只是一个状态值,因此刷新页面的时候改变的值回还原初始化,因此需要结合sessionStorage,代码如下:

1
2
sessionStorage.setItem('loginState',this.userName)
this.$store.commit('changeState',{val: true});

最后在页面判断是否登陆,代码如下

vuex最简单、最详细的入门文档

发表于 2018-04-03

如果你在使用 vue.js , 那么我想你可能会对 vue 组件之间的通信感到崩溃 。

我在使用基于 vue.js 2.0 的UI框架 ElementUI 开发网站的时候 , 就遇到了这种问题 : 一个页面有很多表单 , 我试图将表单写成一个单文件组件 , 但是表单 ( 子组件 ) 里的数据和页面 ( 父组件 ) 按钮交互的时候 , 它们之间的通讯很麻烦 :

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
<!--父组件中引入子组件-->
<template>
<div>
<a href="javascript:;" @click="show = true">点击</a>
<t-dialog :show.sync="show"></t-dialog>
</div>
</template>
<script>
import dialog from './components/dialog.vue'
export default {
data(){
return {
show:false
}
},
components:{
"t-dialog":dialog
}
}
</script>
<!--子组件-->
<template>
<el-dialog :visible.sync="currentShow"></el-dialog>
</template>
<script>
export default {
props:['show'],
computed:{
currentShow:{
get(){
return this.show
},
set(val){
this.$emit("update:show",val)
}
}
}
}
</script>

之所以这么麻烦 , 是因为父组件可以通过 props 给子组件传递参数 , 但子组件内却不能直接修改父组件传过来的参数。

这时候 , 使用 vuex 就可以比较方便的解决这种问题了 :

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
<!--父组件中引入子组件-->
<template>
<div>
<a href="javascript:;" @click="$store.state.show = true">点击</a>
<t-dialog></t-dialog>
</div>
</template>
<script>
import dialog from './components/dialog.vue'
export default {
components:{
"t-dialog":dialog
}
}
</script>
<!--子组件-->
<template>
<el-dialog :visible.sync="$store.state.show"></el-dialog>
</template>
<script>
export default {}
</script>

是不是方便了许多 , 这就是 vuex 最简单的应用 , 不要被网上其他教程吓到 , vuex 原来可以这么简单 !

安装、使用 vuex
首先我们在 vue.js 2.0 开发环境中安装 vuex :
npm install vuex –save

然后 , 在 main.js 中加入 :

1
2
3
4
5
6
7
import vuex from 'vuex'
Vue.use(vuex);
var store = new vuex.Store({//store对象
state:{
show:false
}
})

再然后 , 在实例化 Vue对象时加入 store 对象 :

1
2
3
4
5
6
7
new Vue({
el: '#app',
router,
store,//使用store
template: '<App/>',
components: { App }
})

完成到这一步 , 上述例子中的 $store.state.show 就可以使用了。

modules

前面为了方便 , 我们把 store 对象写在了 main.js 里面 , 但实际上为了便于日后的维护 , 我们分开写更好 , 我们在 src 目录下 , 新建一个 store 文件夹 , 然后在里面新建一个 index.js :

1
2
3
4
5
6
7
8
9
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);
export default new vuex.Store({
state:{
show:false
}
})

那么相应的 , 在 main.js 里的代码应该改成 :

1
2
3
4
5
6
7
8
9
10
//vuex
import store from './store'
new Vue({
el: '#app',
router,
store,//使用store
template: '<App/>',
components: { App }
})

这样就把 store 分离出去了 , 那么还有一个问题是 : 这里 $store.state.show 无论哪个组件都可以使用 , 那组件多了之后 , 状态也多了 , 这么多状态都堆在 store 文件夹下的 index.js 不好维护怎么办 ?

我们可以使用 vuex 的 modules , 把 store 文件夹下的 index.js 改成 :

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);
import dialog_store from '../components/dialog_store.js';//引入某个store对象
export default new vuex.Store({
modules: {
dialog: dialog_store
}
})

这里我们引用了一个 dialog_store.js , 在这个 js 文件里我们就可以单独写 dialog 组件的状态了 :

1
2
3
4
5
export default {
state:{
show:false
}
}

做出这样的修改之后 , 我们将之前我们使用的 $store.state.show 统统改为 $store.state.dialog.show 即可。

如果还有其他的组件需要使用 vuex , 就新建一个对应的状态文件 , 然后将他们加入 store 文件夹下的 index.js 文件中的 modules 中。

1
2
3
4
modules: {
dialog: dialog_store,
other: other,//其他组件
}

mutations

前面我们提到的对话框例子 , 我们对vuex 的依赖仅仅只有一个 $store.state.dialog.show 一个状态 , 但是如果我们要进行一个操作 , 需要依赖很多很多个状态 , 那管理起来又麻烦了 !

mutations 登场 , 问题迎刃而解 :

1
2
3
4
5
6
7
8
9
10
11
export default {
state:{//state
show:false
},
mutations:{
switch_dialog(state){//这里的state对应着上面这个state
state.show = state.show?false:true;
//你还可以在这里执行其他的操作改变state
}
}
}

使用 mutations 后 , 原先我们的父组件可以改为 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div id="app">
<a href="javascript:;" @click="$store.commit('switch_dialog')">点击</a>
<t-dialog></t-dialog>
</div>
</template>
<script>
import dialog from './components/dialog.vue'
export default {
components:{
"t-dialog":dialog
}
}
</script>

使用 $store.commit(‘switch_dialog’) 来触发 mutations 中的 switch_dialog 方法。

这里需要注意的是:

1. mutations 中的方法是不分组件的 , 假如你在 dialog_stroe.js 文件中的定义了

switch_dialog 方法 , 在其他文件中的一个 switch_dialog 方法 , 那么
$store.commit(‘switch_dialog’) 会执行所有的 switch_dialog 方法。

2. mutations里的操作必须是同步的。

你一定好奇 , 如果在 mutations 里执行异步操作会发生什么事情 , 实际上并不会发生什么奇怪的事情 , 只是官方推荐 , 不要在 mutationss 里执行异步操作而已。

actions

多个 state 的操作 , 使用 mutations 会来触发会比较好维护 , 那么需要执行多个 mutations 就需要用 action 了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default {
state:{//state
show:false
},
mutations:{
switch_dialog(state){//这里的state对应着上面这个state
state.show = state.show?false:true;
//你还可以在这里执行其他的操作改变state
}
},
actions:{
switch_dialog(context){//这里的context和我们使用的$store拥有相同的对象和方法
context.commit('switch_dialog');
//你还可以在这里触发其他的mutations方法
},
}
}

那么 , 在之前的父组件中 , 我们需要做修改 , 来触发 action 里的 switch_dialog 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div id="app">
<a href="javascript:;" @click="$store.dispatch('switch_dialog')">点击</a>
<t-dialog></t-dialog>
</div>
</template>
<script>
import dialog from './components/dialog.vue'
export default {
components:{
"t-dialog":dialog
}
}
</script>

使用 $store.dispatch(‘switch_dialog’) 来触发 action 中的 switch_dialog 方法。

官方推荐 , 将异步操作放在 action 中。

getters

getters 和 vue 中的 computed 类似 , 都是用来计算 state 然后生成新的数据 ( 状态 ) 的。

还是前面的例子 , 假如我们需要一个与状态 show 刚好相反的状态 , 使用 vue 中的 computed 可以这样算出来 :

1
2
3
4
5
computed(){
not_show(){
return !this.$store.state.dialog.show;
}
}

那么 , 如果很多很多个组件中都需要用到这个与 show 刚好相反的状态 , 那么我们需要写很多很多个 not_show , 使用 getters 就可以解决这种问题 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default {
state:{//state
show:false
},
getters:{
not_show(state){//这里的state对应着上面这个state
return !state.show;
}
},
mutations:{
switch_dialog(state){//这里的state对应着上面这个state
state.show = state.show?false:true;
//你还可以在这里执行其他的操作改变state
}
},
actions:{
switch_dialog(context){//这里的context和我们使用的$store拥有相同的对象和方法
context.commit('switch_dialog');
//你还可以在这里触发其他的mutations方法
},
}
}

我们在组件中使用 $store.state.dialog.show 来获得状态 show , 类似的 , 我们可以使用 $store.getters.not_show 来获得状态 not_show 。

注意 : $store.getters.not_show 的值是不能直接修改的 , 需要对应的 state 发生变化才能修改。

mapState、mapGetters、mapActions

很多时候 , $store.state.dialog.show 、$store.dispatch(‘switch_dialog’) 这种写法又长又臭 , 很不方便 , 我们没使用 vuex 的时候 , 获取一个状态只需要 this.show , 执行一个方法只需要 this.switch_dialog 就行了 , 使用 vuex 使写法变复杂了 ?使用 mapState、mapGetters、mapActions 就不会这么复杂了。以 mapState 为例 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<el-dialog :visible.sync="show"></el-dialog>
</template>
<script>
import {mapState} from 'vuex';
export default {
computed:{
//这里的三点叫做 : 扩展运算符
...mapState([
show:state=>state.dialog.show
]),
}
}
</script>

相当于 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<el-dialog :visible.sync="show"></el-dialog>
</template>
<script>
import {mapState} from 'vuex';
export default {
computed:{
show(){
return this.$store.state.dialog.show;
}
}
}
</script>

mapGetters、mapActions 和 mapState 类似 , mapGetters 一般也写在 computed 中 , mapActions 一般写在 methods 中。弄懂上面这些 , 你可以去看vuex文档了 , 应该能看懂了。

数组处理方法

发表于 2018-04-03

去除数组中重复对象 思路 :给一个空对象,遍历数组,给空对象赋值,对象的键是数组每项的对象,然后值随便,这样重复的键,赋值同样的值,只会显示一个,达到排重的作用,最后使用Object.keys()方法提取对象的键反到一个数组中

1
2
3
4
5
6
7
8
9
10
11
12
function newArr(arr){
console.log(arr)
var unique = {};
arr.forEach(function(a){
console.log(JSON.stringify(a))
unique[ JSON.stringify(a) ] = 1
});
console.log( unique )
console.log(Object.keys(unique))
arr= Object.keys(unique).map(function(u){return JSON.parse(u) });
return arr;
}

去除空对象

1
2
3
4
5
6
7
8
let newArr=[];
for(let j in arr){
for(let prop in arr[j]){
if(prop!=''||arr[j][prop]!=''){
newArr.push(arr[j]);
}
}
};

数组排序

1
2
3
4
5
6
7
8
9
10
var arr = [{name: "zlw", age: 24}, {name: "wlz", age: 25},{name: "alz", age: 20}];
function compare(property){
return function(a,b){
console.log(a,b)
var value1 = a[property];
var value2 = b[property];
return value2 - value1;
}
}
console.log(arr.sort(compare("age")));

如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。

如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:

● 若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。

● 若 a 等于 b,则返回 0。

● 若 a 大于 b,则返回一个大于 0 的值。

地址栏解析参数

发表于 2018-04-03

方法一:返回url所有参数

1
2
3
4
5
6
7
8
9
10
11
## var queryUrlPar = function (url) {
url = url || window.location.href;
var reg = /([^?=&]+)=([^?=&]+)/g;
var obj = {};
url.replace(reg, function () {
obj[arguments[1]] = arguments[2];
});
return obj;
}
```
## 方法二:指定需要的参数名和url

var href= location.href=”file:///C:/Users/Administrator/Desktop/demo.html?channel=12”;
console.log(getQuery(‘channel’,href));
function getQuery(key, url) {
url = url || window.location.href + ‘’;
if (url.indexOf(‘#’) !== -1) {
url = url.substring(0, url.indexOf(‘#’));
}
var rts = [], rt;
var queryReg = new RegExp(‘(^|\?|&)’ + key + ‘=([^&]*)(?=&|#|$)’, ‘g’);
while ((rt = queryReg.exec(url)) != null) {
rts.push(decodeURIComponent(rt[2]));
}
if (rts.length == 0) return null;
if (rts.length == 1) return rts[0];
return rts;
}
```

12
邵金东

邵金东

18 日志
RSS
© 2018 邵金东
由 Hexo 强力驱动
主题 - NexT.Pisces
本站访客数: